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