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