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