001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024–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; 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; 022 023import java.util.Collection; 024import java.util.List; 025import java.util.Optional; 026 027import java.util.concurrent.CopyOnWriteArrayList; 028 029import java.util.function.Supplier; 030 031import javax.lang.model.AnnotatedConstruct; 032 033import javax.lang.model.element.AnnotationMirror; 034import javax.lang.model.element.Element; 035import javax.lang.model.element.QualifiedNameable; 036 037import javax.lang.model.type.TypeMirror; 038 039import org.microbean.construct.constant.Constables; 040 041import org.microbean.construct.element.UniversalAnnotation; 042import org.microbean.construct.element.UniversalElement; 043 044import org.microbean.construct.type.UniversalType; 045 046import static java.lang.constant.ConstantDescs.BSM_INVOKE; 047import static java.lang.constant.ConstantDescs.CD_List; 048 049import static java.lang.constant.MethodHandleDesc.ofConstructor; 050 051import static java.util.Objects.requireNonNull; 052 053/** 054 * An abstract implementation of {@link AnnotatedConstruct} from which only {@link UniversalElement} and {@link 055 * UniversalType} descend. 056 * 057 * @param <T> a type of {@link AnnotatedConstruct}, which may be only either {@link Element} or {@link TypeMirror} 058 * 059 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 060 * 061 * @see AnnotatedConstruct 062 * 063 * @see UniversalElement 064 * 065 * @see UniversalType 066 */ 067public abstract sealed class UniversalConstruct<T extends AnnotatedConstruct> 068 implements AnnotatedConstruct, Cloneable, Constable 069 permits UniversalElement, UniversalType { 070 071 072 /* 073 * Static fields. 074 */ 075 076 077 private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; 078 079 080 /* 081 * Instance fields. 082 */ 083 084 085 private final PrimordialDomain domain; 086 087 // Eventually this should become a lazy constant/stable value 088 // volatile not needed 089 private Supplier<? extends T> delegateSupplier; 090 091 // Eventually this should become a lazy constant/stable value 092 private volatile String s; 093 094 // Eventually this should become a lazy constant/stable value 095 private volatile CopyOnWriteArrayList<AnnotationMirror> annotations; 096 097 098 /* 099 * Constructors. 100 */ 101 102 103 /** 104 * Creates a new {@link UniversalConstruct} that is a copy of the supplied {@link UniversalConstruct}. 105 * 106 * @param uc a non-{@code null} {@link UniversalConstruct} 107 * 108 * @exception NullPointerException if {@code uc} is {@code null} 109 * 110 * @see #clone() 111 */ 112 protected UniversalConstruct(final UniversalConstruct<T> uc) { 113 super(); 114 this.domain = uc.domain; 115 this.delegateSupplier = uc.delegateSupplier; 116 this.s = uc.s; 117 final Collection<? extends AnnotationMirror> annotations = uc.annotations; // volatile read 118 if (annotations != null) { 119 this.annotations = new CopyOnWriteArrayList<>(annotations); 120 } 121 } 122 123 /** 124 * Creates a new {@link UniversalConstruct}. 125 * 126 * @param delegate a delegate to which operations are delegated; must not be {@code null} 127 * 128 * @param domain a {@link PrimordialDomain} representing the construct domain from which the supplied {@code 129 * delegate} is presumed to have originated; must not be {@code null} 130 * 131 * @exception NullPointerException if either argument is {@code null} 132 * 133 * @see #UniversalConstruct(List, AnnotatedConstruct, PrimordialDomain) 134 */ 135 protected UniversalConstruct(final T delegate, final PrimordialDomain domain) { 136 this(null, delegate, domain); 137 } 138 139 /** 140 * Creates a new {@link UniversalConstruct}. 141 * 142 * @param annotations a {@link List} of {@link AnnotationMirror} instances representing annotations, often 143 * synthetic, that this {@link UniversalConstruct} should bear; may be {@code null} in which case only the annotations 144 * from the supplied {@code delegate} will be used 145 * @param delegate a delegate to which operations are delegated; must not be {@code null} 146 * 147 * @param domain a {@link PrimordialDomain} representing the construct domain from which the supplied {@code 148 * delegate} is presumed to have originated; must not be {@code null} 149 * 150 * @exception NullPointerException if any argument is {@code null} 151 */ 152 @SuppressWarnings("try") 153 protected UniversalConstruct(final List<? extends AnnotationMirror> annotations, 154 final T delegate, 155 final PrimordialDomain domain) { 156 super(); 157 this.domain = requireNonNull(domain, "domain"); 158 if (annotations != null) { 159 this.annotations = new CopyOnWriteArrayList<>(annotations); 160 } 161 final T unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate")); 162 if (unwrappedDelegate == delegate) { 163 this.delegateSupplier = () -> { 164 try (var lock = domain.lock()) { 165 // No unwrapping happened so do symbol completion early; most common case. 166 if (unwrappedDelegate instanceof Element) { 167 ((Element)unwrappedDelegate).getModifiers(); 168 } else { 169 ((TypeMirror)unwrappedDelegate).getKind(); 170 } 171 this.delegateSupplier = () -> unwrappedDelegate; 172 } 173 return unwrappedDelegate; 174 }; 175 } else { 176 assert delegate instanceof UniversalConstruct; 177 // Symbol completion already happened because unwrapping actually happened 178 this.delegateSupplier = () -> unwrappedDelegate; 179 } 180 } 181 182 183 /* 184 * Instance methods. 185 */ 186 187 188 @Override // Cloneable 189 @SuppressWarnings("unchecked") 190 protected UniversalConstruct<T> clone() { 191 final UniversalConstruct<T> clone; 192 try { 193 clone = (UniversalConstruct<T>)super.clone(); 194 } catch (final CloneNotSupportedException e) { 195 throw new AssertionError(e.getMessage(), e); 196 } 197 if (clone.annotations != null) { 198 clone.annotations = new CopyOnWriteArrayList<>(this.getAnnotationMirrors()); 199 } 200 return clone; 201 } 202 203 /** 204 * Returns the delegate to which operations are delegated. 205 * 206 * <p>The delegate is guaranteed not to be an instance of {@link UniversalConstruct}.</p> 207 * 208 * @return a non-{@code null} delegate 209 * 210 * @see Element 211 * 212 * @see TypeMirror 213 */ 214 public final T delegate() { 215 final T delegate = this.delegateSupplier.get(); 216 assert !(delegate instanceof UniversalConstruct); 217 return delegate; 218 } 219 220 @Override // Constable 221 public final Optional<DynamicConstantDesc<T>> describeConstable() { 222 final PrimordialDomain primordialDomain = this.domain(); 223 if (domain instanceof Domain d && d instanceof Constable dc) { 224 final T delegate = this.delegate(); 225 final List<AnnotationMirror> annotations = this.annotations; // volatile read; may be null and that's OK 226 return Constables.describe(delegate, d) 227 .flatMap(delegateDesc -> Constables.describe(annotations) 228 .map(annosDesc -> DynamicConstantDesc.ofNamed(BSM_INVOKE, 229 this.getClass().getSimpleName(), 230 this.getClass().describeConstable().orElseThrow(), 231 ofConstructor(this.getClass().describeConstable().orElseThrow(), 232 CD_List, 233 (delegate instanceof TypeMirror ? TypeMirror.class : Element.class).describeConstable().orElseThrow(), 234 PrimordialDomain.class.describeConstable().orElseThrow()), 235 annosDesc, 236 delegateDesc, 237 dc.describeConstable().orElseThrow()))); 238 } 239 return Optional.empty(); 240 } 241 242 /** 243 * Returns the {@link PrimordialDomain} supplied at construction time. 244 * 245 * @return the non-{@code null} {@link PrimordialDomain} supplied at construction time 246 */ 247 public final PrimordialDomain domain() { 248 return this.domain; 249 } 250 251 @Override // Object 252 public final boolean equals(final Object other) { 253 // Interesting; equality does not cause symbol completion. See: 254 // 255 // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L553-L559 256 // (the only type that overrides this is ArrayType; see 257 // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L1402-L1406) 258 // 259 // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java 260 // (Symbol (Element) doesn't override it at all.) 261 return this == other || switch (other) { 262 case null -> false; 263 case UniversalConstruct<?> uc when this.getClass() == uc.getClass() -> this.delegate().equals(uc.delegate()); 264 default -> false; 265 }; 266 } 267 268 /** 269 * Returns a non-{@code null}, determinate, <strong>mutable</strong>, thread-safe {@link List} of {@link 270 * AnnotationMirror} instances representing the annotations to be considered <dfn>directly present</dfn> on this 271 * {@link UniversalConstruct} implementation. 272 * 273 * @return a non-{@code null}, determinate, <strong>mutable</strong> thread-safe {@link List} of {@link 274 * AnnotationMirror}s 275 * 276 * @see AnnotatedConstruct#getAnnotationMirrors() 277 */ 278 @Override // AnnotatedConstruct 279 @SuppressWarnings("try") 280 public final List<AnnotationMirror> getAnnotationMirrors() { 281 CopyOnWriteArrayList<AnnotationMirror> annotations = this.annotations; // volatile read 282 if (annotations == null) { 283 try (var lock = this.domain().lock()) { 284 this.annotations = annotations = // volatile write 285 new CopyOnWriteArrayList<>(UniversalAnnotation.of(this.delegate().getAnnotationMirrors(), this.domain())); 286 } 287 } 288 return annotations; 289 } 290 291 /** 292 * Makes a <strong>best effort</strong> to return an {@link Annotation} of the appropriate type <dfn>present</dfn> on 293 * this {@link UniversalConstruct} implementation. 294 * 295 * <p>See the specification for the {@link AnnotatedConstruct#getAnnotation(Class)} method for important details.</p> 296 * 297 * <p>{@link UniversalConstruct} implementations deliberately permit modification of their {@linkplain 298 * #getAnnotationMirrors() annotations}. Consequently, this override first checks to see if there is at least one 299 * {@link AnnotationMirror} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type} is declared by a 300 * {@link javax.lang.model.element.TypeElement} whose {@linkplain 301 * javax.lang.model.element.TypeElement#getQualifiedName() qualified name} is {@linkplain 302 * javax.lang.model.element.Name#contentEquals(CharSequence) equal to} the {@linkplain Class#getCanonicalName() 303 * canonical name} of the supplied {@link Class}. If there is, then the {@link AnnotatedConstruct#getAnnotation(Class) 304 * getAnnotation(Class)} method is invoked on the {@linkplain #delegate() delegate} and its result is 305 * returned. Otherwise, {@code null} is returned.</p> 306 * 307 * <p>There are circumstances where the {@link Annotation} returned by this method may not accurately reflect a 308 * synthetic annotation added to this {@link AnnotatedConstruct} implementation's {@linkplain #getAnnotationMirrors() 309 * annotations}.</p> 310 * 311 * <p>In general, the use of this method is discouraged.</p> 312 * 313 * @param annotationType a {@link Class} that is an annotation interface; must not be {@code null} 314 * 315 * @return an appropriate {@link Annotation}, or {@code null} 316 * 317 * @exception NullPointerException if {@code annotationType} is {@code null} 318 * 319 * @see AnnotatedConstruct#getAnnotation(Class) 320 * 321 * @deprecated The use of this method is discouraged. 322 */ 323 @Deprecated 324 @Override // AnnotatedConstruct 325 @SuppressWarnings("try") 326 public final <A extends Annotation> A getAnnotation(final Class<A> annotationType) { 327 if (!annotationType.isAnnotation()) { 328 return null; 329 } 330 final String canonicalName = annotationType.getCanonicalName(); 331 for (final AnnotationMirror a : this.getAnnotationMirrors()) { 332 if (((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().contentEquals(canonicalName)) { 333 // TODO: is this lock actually needed, given how delegateSupplier works? 334 try (var lock = this.domain().lock()) { 335 return this.delegate().getAnnotation(annotationType); 336 } 337 } 338 } 339 return null; 340 } 341 342 /** 343 * Makes a <strong>best effort</strong> to return an array of {@link Annotation}s of the appropriate type 344 * <dfn>associated</dfn> with this {@link UniversalConstruct} implementation. 345 * 346 * <p>See the specification for the {@link AnnotatedConstruct#getAnnotationsByType(Class)} method for important 347 * details.</p> 348 * 349 * <p>{@link UniversalConstruct} implementations deliberately permit modification of their {@linkplain 350 * #getAnnotationMirrors() annotations}. Consequently, this override first checks to see if there is at least one 351 * {@link AnnotationMirror} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type} is declared by a 352 * {@link javax.lang.model.element.TypeElement} whose {@linkplain 353 * javax.lang.model.element.TypeElement#getQualifiedName() qualified name} is {@linkplain 354 * javax.lang.model.element.Name#contentEquals(CharSequence) equal to} the {@linkplain Class#getCanonicalName() 355 * canonical name} of the supplied {@link Class}. If there is, then the {@link 356 * AnnotatedConstruct#getAnnotationsByType(Class) getAnnotationsByType(Class)} method is invoked on the {@linkplain 357 * #delegate() delegate} and its result is returned. Otherwise, an empty array is returned.</p> 358 * 359 * <p>There are circumstances where the {@link Annotation} array returned by this method may not accurately reflect 360 * synthetic annotations added to this {@link AnnotatedConstruct} implementation's {@linkplain #getAnnotationMirrors() 361 * annotations}.</p> 362 * 363 * <p>In general, the use of this method is discouraged.</p> 364 * 365 * @param annotationType a {@link Class} that is an annotation interface; must not be {@code null} 366 * 367 * @return an appropriate {@link Annotation}, or {@code null} 368 * 369 * @exception NullPointerException if {@code annotationType} is {@code null} 370 * 371 * @see AnnotatedConstruct#getAnnotation(Class) 372 * 373 * @deprecated The use of this method is discouraged. 374 */ 375 @Deprecated 376 @Override // AnnotatedConstruct 377 @SuppressWarnings({"try", "unchecked"}) 378 public final <A extends Annotation> A[] getAnnotationsByType(final Class<A> annotationType) { 379 if (!annotationType.isAnnotation()) { 380 return (A[])EMPTY_ANNOTATION_ARRAY; 381 } 382 final String canonicalName = annotationType.getCanonicalName(); 383 for (final AnnotationMirror a : this.getAnnotationMirrors()) { 384 if (((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().contentEquals(canonicalName)) { 385 // TODO: is this lock actually needed, given how delegateSupplier works? 386 try (var lock = this.domain().lock()) { 387 return this.delegate().getAnnotationsByType(annotationType); 388 } 389 } 390 } 391 return (A[])EMPTY_ANNOTATION_ARRAY; 392 } 393 394 @Override // Object 395 public final int hashCode() { 396 // Interesting; hashCode doesn't cause symbol completion. See: 397 // 398 // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L565-L568 399 // (AnnoConstruct doesn't define it so super.hashCode() is Object.hashCode()) 400 // 401 // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java 402 // (Symbol (Element) doesn't override it at all.) 403 return this.delegate().hashCode(); 404 } 405 406 @Override // Object 407 @SuppressWarnings("try") 408 public final String toString() { 409 String s = this.s; // volatile read 410 if (s == null) { 411 try (var lock = this.domain().lock()) { 412 s = this.s = this.delegate().toString(); // volatile write, read 413 } 414 } 415 return s; 416 } 417 418 419 /* 420 * Static methods. 421 */ 422 423 424 /** 425 * <dfn>Unwraps</dfn> the supplied {@link AnnotatedConstruct} implementation such that the returned value is not an 426 * instance of {@link UniversalConstruct}. 427 * 428 * @param <T> an {@link AnnotatedConstruct} subtype (possibly {@link UniversalElement} or {@link UniversalType}) 429 * 430 * @param t an {@link AnnotatedConstruct}; may be {@code null} 431 * 432 * @return an object of the appropriate type that is guaranteed not to be an instance of {@link UniversalConstruct} 433 * 434 * @see #delegate() 435 */ 436 @SuppressWarnings("unchecked") 437 public static final <T extends AnnotatedConstruct> T unwrap(T t) { 438 while (t instanceof UniversalConstruct<?> uc) { 439 t = (T)uc.delegate(); 440 } 441 return t; 442 } 443 444}