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