001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024–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.util.ArrayList; 017import java.util.Collection; 018import java.util.Collections; 019import java.util.List; 020import java.util.Optional; 021import java.util.Set; 022 023import java.util.function.Supplier; 024 025import javax.lang.model.element.AnnotationMirror; 026import javax.lang.model.element.Element; 027import javax.lang.model.element.ElementKind; 028import javax.lang.model.element.ElementVisitor; 029import javax.lang.model.element.ExecutableElement; 030import javax.lang.model.element.Modifier; 031import javax.lang.model.element.ModuleElement; 032import javax.lang.model.element.NestingKind; 033import javax.lang.model.element.PackageElement; 034import javax.lang.model.element.Parameterizable; 035import javax.lang.model.element.QualifiedNameable; 036import javax.lang.model.element.RecordComponentElement; 037import javax.lang.model.element.TypeElement; 038import javax.lang.model.element.TypeParameterElement; 039import javax.lang.model.element.VariableElement; 040 041import javax.lang.model.type.TypeKind; 042import javax.lang.model.type.TypeMirror; 043 044import org.microbean.construct.UniversalConstruct; 045import org.microbean.construct.PrimordialDomain; 046 047import org.microbean.construct.type.UniversalType; 048 049/** 050 * An {@link Element} and {@link UniversalConstruct} implementation. 051 * 052 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 053 * 054 * @see Element#getKind() 055 * 056 * @see UniversalConstruct 057 */ 058@SuppressWarnings("preview") 059public final class UniversalElement 060 extends UniversalConstruct<Element, UniversalElement> 061 implements ExecutableElement, 062 ModuleElement, 063 PackageElement, 064 RecordComponentElement, 065 TypeElement, 066 TypeParameterElement, 067 VariableElement { 068 069 // volatile not needed 070 private Supplier<? extends List<? extends UniversalElement>> enclosedElementsSupplier; 071 072 /** 073 * Creates a new {@link UniversalElement}. 074 * 075 * @param delegate an {@link Element} to which operations will be delegated; must not be {@code null} 076 * 077 * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated; 078 * must not be {@code null} 079 * 080 * @exception NullPointerException if either argument is {@code null} 081 */ 082 public UniversalElement(final Element delegate, final PrimordialDomain domain) { 083 this(delegate, null, domain); 084 } 085 086 /** 087 * Creates a new {@link UniversalElement}. 088 * 089 * @param delegate an {@link Element} to which operations will be delegated; must not be {@code null} 090 * 091 * @param annotations a {@link List} of {@link AnnotationMirror} instances representing annotations, often synthetic, 092 * that this {@link UniversalElement} should bear; may be {@code null} in which case only the annotations from the 093 * supplied {@code delegate} will be used 094 * 095 * @param domain a {@link PrimordialDomain} from which the supplied {@code delegate} is presumed to have originated; 096 * must not be {@code null} 097 * 098 * @exception NullPointerException if any argument is {@code null} 099 * 100 * @see #delegate() 101 */ 102 @SuppressWarnings("try") 103 private UniversalElement(final Element delegate, 104 final List<? extends AnnotationMirror> annotations, 105 final PrimordialDomain domain) { 106 super(delegate, annotations, domain); 107 this.enclosedElementsSupplier = () -> { 108 final List<? extends UniversalElement> ees; 109 try (var lock = domain.lock()) { 110 ees = this.wrap(this.delegate().getEnclosedElements()); 111 this.enclosedElementsSupplier = () -> ees; 112 } 113 return ees; 114 }; 115 } 116 117 @Override // Element 118 public final <R, P> R accept(final ElementVisitor<R, P> v, final P p) { 119 return switch (this.getKind()) { 120 case 121 ANNOTATION_TYPE, 122 CLASS, 123 ENUM, 124 INTERFACE, 125 RECORD -> v.visitType(this, p); 126 case TYPE_PARAMETER -> v.visitTypeParameter(this, p); 127 case 128 BINDING_VARIABLE, 129 ENUM_CONSTANT, 130 EXCEPTION_PARAMETER, 131 FIELD, 132 LOCAL_VARIABLE, 133 PARAMETER, 134 RESOURCE_VARIABLE -> v.visitVariable(this, p); 135 case RECORD_COMPONENT -> v.visitRecordComponent(this, p); 136 case 137 CONSTRUCTOR, 138 INSTANCE_INIT, 139 METHOD, 140 STATIC_INIT -> v.visitExecutable(this, p); 141 case PACKAGE -> v.visitPackage(this, p); 142 case MODULE -> v.visitModule(this, p); 143 case OTHER -> v.visitUnknown(this, p); 144 }; 145 } 146 147 @Override // UniversalConstruct<Element, UniversalElement<Element>> 148 protected final UniversalElement annotate(final List<? extends AnnotationMirror> replacementAnnotations) { 149 return new UniversalElement(this.delegate(), replacementAnnotations, this.domain()); 150 } 151 152 @Override // Element 153 public final UniversalType asType() { 154 return UniversalType.of(this.delegate().asType(), this.domain()); 155 } 156 157 /** 158 * Returns {@code true} if and only if this {@link UniversalElement} is a <dfn>generic class declaration</dfn>. 159 * 160 * @return {@code true} if and only if this {@link UniversalElement} is a <dfn>generic class declaration</dfn> 161 * 162 * @spec https://docs.oracle.com/javase/specs/jls/se23/html/jls-8.html#jls-8.1.2 Java Language Specification, section 163 * 8.1.2 164 */ 165 public final boolean generic() { 166 return switch (this.getKind()) { 167 case CLASS, CONSTRUCTOR, ENUM, INTERFACE, METHOD, RECORD -> !this.getTypeParameters().isEmpty(); 168 default -> false; 169 }; 170 } 171 172 @Override // RecordComponentElement 173 public final UniversalElement getAccessor() { 174 return switch (this.getKind()) { 175 case RECORD_COMPONENT -> this.wrap(((RecordComponentElement)this.delegate()).getAccessor()); 176 default -> null; 177 }; 178 } 179 180 @Override // TypeParameterElement 181 public final List<? extends UniversalType> getBounds() { 182 return switch (this.getKind()) { 183 case TYPE_PARAMETER -> UniversalType.of(((TypeParameterElement)this.delegate()).getBounds(), this.domain()); 184 default -> List.of(); 185 }; 186 } 187 188 @Override // VariableElement 189 @SuppressWarnings("try") 190 public final Object getConstantValue() { 191 return switch (this.getKind()) { 192 case BINDING_VARIABLE, ENUM_CONSTANT, EXCEPTION_PARAMETER, FIELD, LOCAL_VARIABLE, PARAMETER, RESOURCE_VARIABLE -> { 193 try (var lock = this.domain().lock()) { 194 // There is a LOT going on here; take the domain lock for safety. See 195 // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java#L1813-L1829 196 yield 197 ((VariableElement)this.delegate()).getConstantValue(); // will be a boxed type or String 198 } 199 } 200 default -> null; 201 }; 202 } 203 204 @Override // ExecutableElement 205 public final UniversalAnnotationValue getDefaultValue() { 206 return switch (this.getKind()) { 207 // See 208 // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java#L2287-L2290; 209 // no concurrent Name access so no lock needed 210 case METHOD -> UniversalAnnotationValue.of(((ExecutableElement)this.delegate()).getDefaultValue(), this.domain()); 211 default -> null; 212 }; 213 } 214 215 @Override // ModuleElement 216 public final List<? extends UniversalDirective> getDirectives() { 217 return switch (this.getKind()) { 218 case MODULE -> UniversalDirective.of(((ModuleElement)this.delegate()).getDirectives(), this.domain()); 219 default -> List.of(); 220 }; 221 } 222 223 /* 224 java.lang.AssertionError: Filling jrt:/java.base/java/lang/String$CaseInsensitiveComparator.class during DirectoryFileObject[/modules/java.base:java/lang/Integer$IntegerCache.class] 225 at jdk.compiler/com.sun.tools.javac.util.Assert.error(Assert.java:162) 226 at jdk.compiler/com.sun.tools.javac.code.ClassFinder.fillIn(ClassFinder.java:366) 227 at jdk.compiler/com.sun.tools.javac.code.ClassFinder.complete(ClassFinder.java:302) 228 at jdk.compiler/com.sun.tools.javac.code.Symbol.complete(Symbol.java:687) 229 at jdk.compiler/com.sun.tools.javac.code.Symbol$ClassSymbol.complete(Symbol.java:1455) 230 at jdk.compiler/com.sun.tools.javac.code.Symbol.apiComplete(Symbol.java:693) 231 at jdk.compiler/com.sun.tools.javac.code.Symbol$TypeSymbol.getEnclosedElements(Symbol.java:864) 232 at jdk.compiler/com.sun.tools.javac.code.Symbol$ClassSymbol.getEnclosedElements(Symbol.java:1420) 233 at jdk.compiler/com.sun.tools.javac.code.Symbol$ClassSymbol.getEnclosedElements(Symbol.java:1264) 234 at org.microbean.construct@0.0.10-SNAPSHOT/org.microbean.construct.element.UniversalElement.getEnclosedElements(UniversalElement.java:195) 235 */ 236 // See https://github.com/microbean/microbean-construct/issues/18 237 @Override // Element 238 public final List<? extends UniversalElement> getEnclosedElements() { 239 return this.enclosedElementsSupplier.get(); 240 } 241 242 @Override // Element 243 public final UniversalElement getEnclosingElement() { 244 return this.wrap(this.delegate().getEnclosingElement()); 245 } 246 247 @Override // TypeParameterElement 248 public final UniversalElement getGenericElement() { 249 return switch (this.getKind()) { 250 case TYPE_PARAMETER -> this.wrap(((TypeParameterElement)this.delegate()).getGenericElement()); 251 default -> this.getEnclosingElement(); // illegal state 252 }; 253 } 254 255 @Override // TypeElement 256 public final List<? extends UniversalType> getInterfaces() { 257 return switch (this.getKind()) { 258 case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD -> 259 UniversalType.of(((TypeElement)this.delegate()).getInterfaces(), this.domain()); 260 default -> List.of(); 261 }; 262 } 263 264 @Override // Element 265 public final ElementKind getKind() { 266 return this.delegate().getKind(); 267 } 268 269 @Override // Element 270 public final Set<Modifier> getModifiers() { 271 return this.delegate().getModifiers(); 272 } 273 274 @Override // TypeElement 275 public final NestingKind getNestingKind() { 276 return switch (this.getKind()) { 277 case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD -> ((TypeElement)this.delegate()).getNestingKind(); 278 default -> NestingKind.TOP_LEVEL; // illegal state 279 }; 280 } 281 282 @Override // ExecutableElement 283 public final List<? extends UniversalElement> getParameters() { 284 return switch (this.getKind()) { 285 case CONSTRUCTOR, METHOD -> this.wrap(((ExecutableElement)this.delegate()).getParameters()); 286 default -> List.of(); 287 }; 288 } 289 290 @Override // ModuleElement, PackageElement, TypeElement 291 @SuppressWarnings("try") 292 public final StringName getQualifiedName() { 293 return switch (this.getKind()) { 294 case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, MODULE, PACKAGE, RECORD -> { 295 try (var lock = this.domain().lock()) { 296 yield StringName.of(((QualifiedNameable)this.delegate()).getQualifiedName().toString(), this.domain()); 297 } 298 } 299 default -> StringName.of("", this.domain()); 300 }; 301 } 302 303 @Override // ExecutableElement 304 public final UniversalType getReceiverType() { 305 return 306 this.getKind().isExecutable() ? 307 UniversalType.of(this.asType(), this.domain()).getReceiverType() : 308 UniversalType.of(this.domain().noType(TypeKind.NONE), this.domain()); 309 } 310 311 @Override // TypeElement 312 public final List<? extends UniversalElement> getRecordComponents() { 313 return switch (this.getKind()) { 314 case RECORD -> this.wrap(((TypeElement)this.delegate()).getRecordComponents()); 315 default -> List.of(); 316 }; 317 } 318 319 @Override // ExecutableElement 320 public final UniversalType getReturnType() { 321 return UniversalType.of(this.asType(), this.domain()).getReturnType(); 322 } 323 324 @Override // Element 325 @SuppressWarnings("try") 326 public final StringName getSimpleName() { 327 try (var lock = this.domain().lock()) { 328 return new StringName(this.delegate().getSimpleName().toString(), this.domain()); 329 } 330 } 331 332 @Override // TypeElement 333 public final UniversalType getSuperclass() { 334 return switch (this.getKind()) { 335 case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD -> 336 UniversalType.of(((TypeElement)this.delegate()).getSuperclass(), this.domain()); 337 default -> UniversalType.of(this.domain().noType(TypeKind.NONE), this.domain()); 338 }; 339 } 340 341 @Override // ExecutableElement 342 public final List<? extends UniversalType> getThrownTypes() { 343 return 344 this.getKind().isExecutable() ? 345 UniversalType.of(((ExecutableElement)this.delegate()).getThrownTypes(), this.domain()) : 346 List.of(); 347 } 348 349 @Override // ExecutableElement 350 public final List<? extends UniversalElement> getTypeParameters() { 351 return switch (this.getKind()) { 352 case CLASS, CONSTRUCTOR, ENUM, INTERFACE, RECORD, METHOD -> 353 this.wrap(((Parameterizable)this.delegate()).getTypeParameters()); 354 default -> List.of(); 355 }; 356 } 357 358 @Override // ExecutableElement 359 public final boolean isDefault() { 360 return switch (this.getKind()) { 361 case METHOD -> ((ExecutableElement)this.delegate()).isDefault(); 362 default -> false; 363 }; 364 } 365 366 @Override // ModuleElement 367 public final boolean isOpen() { 368 return switch (this.getKind()) { 369 case MODULE -> ((ModuleElement)this.delegate()).isOpen(); 370 default -> false; 371 }; 372 } 373 374 @Override // ModuleElement, PackageElement 375 public final boolean isUnnamed() { 376 return switch (this.getKind()) { 377 case MODULE -> ((ModuleElement)this.delegate()).isUnnamed(); 378 case PACKAGE -> ((PackageElement)this.delegate()).isUnnamed(); 379 default -> false; 380 }; 381 } 382 383 @Override // ExecutableElement 384 public final boolean isVarArgs() { 385 return switch (this.getKind()) { 386 case CONSTRUCTOR, METHOD -> ((ExecutableElement)this.delegate()).isVarArgs(); 387 default -> false; 388 }; 389 } 390 391 /** 392 * A convenience method that returns {@code true} if this {@link UniversalElement} represents the class declaration 393 * for {@code java.lang.Object}. 394 * 395 * @return {@code true} if this {@link UniversalElement} is the class declaration for {@code java.lang.Object} 396 */ 397 public final boolean javaLangObject() { 398 return switch (this.getKind()) { 399 case CLASS -> this.getQualifiedName().contentEquals("java.lang.Object"); 400 default -> false; 401 }; 402 } 403 404 private final UniversalElement wrap(final Element e) { 405 return of(e, this.domain()); 406 } 407 408 private final List<? extends UniversalElement> wrap(final Collection<? extends Element> es) { 409 return of(es, this.domain()); 410 } 411 412 413 /* 414 * Static methods. 415 */ 416 417 418 /** 419 * Returns a non-{@code null}, immutable {@link List} of {@link UniversalElement}s whose elements wrap the supplied 420 * {@link List}'s elements. 421 * 422 * @param es a {@link Collection} of {@link Element}s; must not be {@code null} 423 * 424 * @param domain a {@link PrimordialDomain}; must not be {@code null} 425 * 426 * @return a non-{@code null}, immutable {@link List} of {@link UniversalElement}s 427 * 428 * @exception NullPointerException if either argument is {@code null} 429 */ 430 public static final List<? extends UniversalElement> of(final Collection<? extends Element> es, final PrimordialDomain domain) { 431 if (es.isEmpty()) { 432 return List.of(); 433 } 434 final List<UniversalElement> newEs = new ArrayList<>(es.size()); 435 for (final Element e : es) { 436 newEs.add(of(e, domain)); 437 } 438 return Collections.unmodifiableList(newEs); 439 } 440 441 /** 442 * Returns a {@link UniversalElement} that is either the supplied {@link Element} (if it itself is {@code null} or is 443 * a {@link UniversalElement}) or one that wraps it. 444 * 445 * @param e an {@link Element}; may be {@code null} in which case {@code null} will be returned 446 * 447 * @param domain a {@link PrimordialDomain}; must not be {@code null} 448 * 449 * @return a {@link UniversalElement}, or {@code null} (if {@code e} is {@code null}) 450 * 451 * @exception NullPointerException if {@code domain} is {@code null} 452 * 453 * @see #UniversalElement(Element, PrimordialDomain) 454 */ 455 public static final UniversalElement of(final Element e, final PrimordialDomain domain) { 456 return switch (e) { 457 case null -> null; 458 case UniversalElement ue -> ue; 459 default -> new UniversalElement(e, domain); 460 }; 461 } 462 463}