001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024 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.vm; 015 016import java.util.ArrayDeque; 017import java.util.Deque; 018import java.util.Iterator; 019import java.util.List; 020 021import javax.lang.model.element.Element; 022import javax.lang.model.element.ElementKind; 023import javax.lang.model.element.ExecutableElement; 024import javax.lang.model.element.PackageElement; 025import javax.lang.model.element.TypeElement; 026import javax.lang.model.element.TypeParameterElement; 027import javax.lang.model.element.VariableElement; 028 029import javax.lang.model.type.ArrayType; 030import javax.lang.model.type.DeclaredType; 031import javax.lang.model.type.TypeKind; 032import javax.lang.model.type.TypeMirror; 033import javax.lang.model.type.TypeVariable; 034import javax.lang.model.type.WildcardType; 035 036import org.microbean.construct.Domain; 037 038/** 039 * A utility class that provides <dfn>signatures</dfn> for {@link TypeMirror}s and {@link Element}s. 040 * 041 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 042 * 043 * @spec https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html#jvms-4.7.9.1 Java Virtual Machine 044 * Specification, section 4.7.9.1 045 */ 046@SuppressWarnings("try") 047public final class Signatures { 048 049 private Signatures() { 050 super(); 051 } 052 053 /** 054 * Returns a <dfn>signature</dfn> for the supplied {@link Element}. 055 * 056 * @param e the {@link Element} for which a signature should be returned; must not be {@code null} 057 * 058 * @param d a {@link Domain} from which the {@link Element} is presumed to have originated; must not be {@code null} 059 * 060 * @exception NullPointerException if either argument is {@code null} 061 * 062 * @exception IllegalArgumentException if {@code e} has an {@link ElementKind} that is either {@link 063 * ElementKind#ANNOTATION_TYPE}, {@link ElementKind#BINDING_VARIABLE}, {@link ElementKind#EXCEPTION_PARAMETER}, {@link 064 * ElementKind#LOCAL_VARIABLE}, {@link ElementKind#MODULE}, {@link ElementKind#OTHER}, {@link ElementKind#PACKAGE}, 065 * {@link ElementKind#RESOURCE_VARIABLE}, or {@link ElementKind#TYPE_PARAMETER} 066 * 067 * @return a non-{@code null} signature 068 * 069 * @see ElementKind 070 * 071 * @spec https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html#jvms-4.7.9.1 Java Virtual Machine 072 * Specification, section 4.7.9.1 073 */ 074 public static final String signature(final Element e, final Domain d) { 075 try (var lock = d.lock()) { 076 return switch (e.getKind()) { 077 case 078 CLASS, 079 ENUM, 080 INTERFACE, 081 RECORD -> classSignature((TypeElement)e, d); 082 case 083 CONSTRUCTOR, 084 INSTANCE_INIT, 085 METHOD, 086 STATIC_INIT -> methodSignature((ExecutableElement)e, d); 087 case 088 ENUM_CONSTANT, 089 FIELD, 090 PARAMETER, 091 RECORD_COMPONENT -> fieldSignature(e, d); 092 case 093 ANNOTATION_TYPE, 094 BINDING_VARIABLE, 095 EXCEPTION_PARAMETER, 096 LOCAL_VARIABLE, 097 MODULE, 098 OTHER, 099 PACKAGE, 100 RESOURCE_VARIABLE, 101 TYPE_PARAMETER -> 102 throw new IllegalArgumentException("e: " + e + "; kind: " + e.getKind()); 103 }; 104 } 105 } 106 107 private static final String classSignature(final TypeElement e, final Domain d) { 108 // Precondition: under domain lock 109 return switch (e.getKind()) { 110 case CLASS, ENUM, INTERFACE, RECORD -> { 111 if (!d.generic(e) && ((DeclaredType)e.getSuperclass()).getTypeArguments().isEmpty()) { 112 boolean signatureRequired = false; 113 for (final TypeMirror iface : e.getInterfaces()) { 114 if (!((DeclaredType)iface).getTypeArguments().isEmpty()) { 115 signatureRequired = true; 116 break; 117 } 118 } 119 if (!signatureRequired) { 120 yield null; 121 } 122 } 123 final StringBuilder sb = new StringBuilder(); 124 classSignature(e, sb, d); 125 yield sb.toString(); 126 } 127 default -> throw new IllegalArgumentException("e: " + e + "; kind: " + e.getKind()); 128 }; 129 } 130 131 private static final void classSignature(final TypeElement e, final StringBuilder sb, final Domain d) { 132 // Precondition: under domain lock 133 switch (e.getKind()) { 134 case CLASS, ENUM, INTERFACE, RECORD -> { // note: no ANNOTATION_TYPE on purpose 135 typeParameters(e.getTypeParameters(), sb, d); 136 final List<? extends TypeMirror> directSupertypes = d.directSupertypes(e.asType()); 137 if (directSupertypes.isEmpty()) { 138 assert e.getQualifiedName().contentEquals("java.lang.Object") : "DeclaredType with no supertypes: " + e.asType(); 139 } else { 140 final DeclaredType firstSupertype = (DeclaredType)directSupertypes.get(0); 141 assert firstSupertype.getKind() == TypeKind.DECLARED; 142 // "For an interface type with no direct super-interfaces, a type mirror representing java.lang.Object is 143 // returned." Therefore in all situations, given a non-empty list of direct supertypes, the first element will 144 // always be a non-interface class. 145 assert !((TypeElement)firstSupertype.asElement()).getKind().isInterface() : "Contract violation"; 146 superclassSignature(firstSupertype, sb, d); 147 superinterfaceSignatures(directSupertypes.subList(1, directSupertypes.size()), sb, d); 148 } 149 break; 150 } 151 default -> throw new IllegalArgumentException("e: " + e + "; kind: " + e.getKind()); 152 } 153 } 154 155 @SuppressWarnings("fallthrough") 156 private static final String methodSignature(final ExecutableElement e, final Domain d) { 157 // Precondition: under domain lock 158 if (e.getKind().isExecutable()) { 159 boolean throwsClauseRequired = false; 160 for (final TypeMirror exceptionType : e.getThrownTypes()) { 161 if (exceptionType.getKind() == TypeKind.TYPEVAR) { 162 throwsClauseRequired = true; 163 break; 164 } 165 } 166 if (!throwsClauseRequired && !d.generic(e)) { 167 final TypeMirror returnType = e.getReturnType(); 168 switch (returnType.getKind()) { 169 case TYPEVAR: 170 break; 171 case DECLARED: 172 if (!((DeclaredType)returnType).getTypeArguments().isEmpty()) { 173 break; 174 } 175 // fall through 176 default: 177 boolean signatureRequired = false; 178 for (final VariableElement p : e.getParameters()) { 179 final TypeMirror parameterType = p.asType(); 180 switch (parameterType.getKind()) { 181 case DECLARED: 182 signatureRequired = !((DeclaredType)parameterType).getTypeArguments().isEmpty(); 183 break; 184 case TYPEVAR: 185 signatureRequired = true; 186 break; 187 default: 188 break; 189 } 190 } 191 if (!signatureRequired) { 192 return null; 193 } 194 } 195 } 196 final StringBuilder sb = new StringBuilder(); 197 methodSignature(e, sb, throwsClauseRequired, d); 198 return sb.toString(); 199 } else { 200 throw new IllegalArgumentException("e: " + e + "; kind: " + e.getKind()); 201 } 202 } 203 204 private static final void methodSignature(final ExecutableElement e, 205 final StringBuilder sb, 206 final boolean throwsClauseRequired, 207 final Domain d) { 208 // Precondition: under domain lock 209 if (!e.getKind().isExecutable()) { 210 throw new IllegalArgumentException("e: " + e + "; kind: " + e.getKind()); 211 } 212 typeParameters(e.getTypeParameters(), sb, d); 213 sb.append('('); 214 parameterSignatures(e.getParameters(), sb, d); 215 sb.append(')'); 216 final TypeMirror returnType = e.getReturnType(); 217 if (returnType.getKind() == TypeKind.VOID) { 218 sb.append('V'); 219 } else { 220 typeSignature(returnType, sb, d); 221 } 222 if (throwsClauseRequired) { 223 throwsSignatures(e.getThrownTypes(), sb, d); 224 } 225 } 226 227 @SuppressWarnings("fallthrough") 228 private static final String fieldSignature(final Element e, final Domain d) { 229 // Precondition: under domain lock 230 return switch (e.getKind()) { 231 case ENUM_CONSTANT, FIELD, PARAMETER, RECORD_COMPONENT -> { 232 final TypeMirror t = e.asType(); 233 switch (t.getKind()) { 234 case DECLARED: 235 if (((DeclaredType)t).getTypeArguments().isEmpty()) { 236 // TODO: is this sufficient? Or do we, for example, have to examine the type's supertypes to see if *they* 237 // "use" a parameterized type? Maybe we have to look at the enclosing type too? But if so, why only here, and 238 // why not the same sort of thing for the return type of a method (see above)? 239 yield null; 240 } 241 // fall through 242 case TYPEVAR: 243 final StringBuilder sb = new StringBuilder(); 244 fieldSignature(e, sb, d); 245 yield sb.toString(); 246 default: 247 // TODO: is this sufficient? Or do we, for example, have to examine the type's supertypes to see if *they* 248 // "use" a parameterized type? Maybe we have to look at the enclosing type too? But if so, why only here, and 249 // why not the same sort of thing for the return type of a method (see above)? 250 yield null; 251 } 252 } 253 default -> throw new IllegalArgumentException("e: " + e + "; kind: " + e.getKind()); 254 }; 255 } 256 257 private static final void fieldSignature(final Element e, final StringBuilder sb, final Domain d) { 258 // Precondition: under domain lock 259 switch (e.getKind()) { 260 case ENUM_CONSTANT, FIELD, PARAMETER, RECORD_COMPONENT -> typeSignature(e.asType(), sb, d); 261 default -> throw new IllegalArgumentException("e: " + e); 262 } 263 } 264 265 private static final void parameterSignatures(final List<? extends VariableElement> ps, 266 final StringBuilder sb, 267 final Domain d) { 268 // Precondition: under domain lock 269 for (final VariableElement p : ps) { 270 if (p.getKind() != ElementKind.PARAMETER) { 271 throw new IllegalArgumentException("ps: " + ps); 272 } 273 typeSignature(p.asType(), sb, d); 274 } 275 } 276 277 private static final void throwsSignatures(final List<? extends TypeMirror> ts, final StringBuilder sb, final Domain d) { 278 // Precondition: under domain lock 279 for (final TypeMirror t : ts) { 280 sb.append(switch (t.getKind()) { 281 case DECLARED, TYPEVAR -> '^'; 282 default -> throw new IllegalArgumentException("ts: " + ts); 283 }); 284 typeSignature(t, sb, d); 285 } 286 } 287 288 private static final void typeParameters(final List<? extends TypeParameterElement> tps, 289 final StringBuilder sb, 290 final Domain d) { 291 if (!tps.isEmpty()) { 292 sb.append('<'); 293 // Precondition: under domain lock 294 for (final TypeParameterElement tp : tps) { 295 switch (tp.getKind()) { 296 case TYPE_PARAMETER -> typeParameter(tp, sb, d); 297 default -> throw new IllegalArgumentException("tps: " + tps); 298 } 299 } 300 sb.append('>'); 301 } 302 } 303 304 private static final void typeParameter(final TypeParameterElement e, final StringBuilder sb, final Domain d) { 305 // Precondition: under domain lock 306 if (e.getKind() != ElementKind.TYPE_PARAMETER) { 307 throw new IllegalArgumentException("e: " + e); 308 } 309 final List<? extends TypeMirror> bounds = e.getBounds(); 310 sb.append(e.getSimpleName()).append(':'); 311 if (bounds.isEmpty()) { 312 sb.append("java.lang.Object"); 313 } else { 314 classBound(bounds.get(0), sb, d); 315 } 316 interfaceBounds(bounds.subList(1, bounds.size()), sb, d); 317 } 318 319 private static final void classBound(final TypeMirror t, final StringBuilder sb, final Domain d) { 320 // Precondition: under domain lock 321 if (t.getKind() != TypeKind.DECLARED) { 322 throw new IllegalArgumentException("t: " + t); 323 } 324 typeSignature(t, sb, d); 325 } 326 327 private static final void interfaceBounds(final List<? extends TypeMirror> ts, final StringBuilder sb, final Domain d) { 328 // Precondition: under domain lock 329 for (final TypeMirror t : ts) { 330 interfaceBound(t, sb, d); 331 } 332 } 333 334 @SuppressWarnings("fallthrough") 335 private static final void interfaceBound(final TypeMirror t, final StringBuilder sb, final Domain d) { 336 // Precondition: under domain lock 337 switch (t.getKind()) { 338 case DECLARED: 339 if (((DeclaredType)t).asElement().getKind().isInterface()) { 340 sb.append(':'); 341 typeSignature(t, sb, d); 342 return; 343 } 344 // fall through 345 default: 346 throw new IllegalArgumentException("t: " + t); 347 } 348 } 349 350 private static final void superclassSignature(final TypeMirror t, final StringBuilder sb, final Domain d) { 351 classTypeSignature(t, sb, d); 352 } 353 354 private static final void superinterfaceSignatures(final List<? extends TypeMirror> ts, 355 final StringBuilder sb, 356 final Domain d) { 357 // Precondition: under domain lock 358 for (final TypeMirror t : ts) { 359 superinterfaceSignature(t, sb, d); 360 } 361 } 362 363 @SuppressWarnings("fallthrough") 364 private static final void superinterfaceSignature(final TypeMirror t, final StringBuilder sb, final Domain d) { 365 // Precondition: under domain lock 366 switch (t.getKind()) { 367 case DECLARED: 368 if (((DeclaredType)t).asElement().getKind().isInterface()) { 369 classTypeSignature(t, sb, d); 370 return; 371 } 372 // fall through 373 default: 374 throw new IllegalArgumentException("t: " + t); 375 } 376 } 377 378 /** 379 * Returns a <dfn>signature</dfn> for the supplied {@link TypeMirror}. 380 * 381 * @param t the {@link TypeMirror} for which a signature should be returned; must not be {@code null} 382 * 383 * @param d a {@link Domain} from which the {@link TypeMirror} is presumed to have originated; must not be {@code 384 * null} 385 * 386 * @exception NullPointerException if either argument is {@code null} 387 * 388 * @exception IllegalArgumentException if {@code t} has an {@link TypeKind} that is either {@link TypeKind#ERROR}, 389 * {@link TypeKind#EXECUTABLE}, {@link TypeKind#INTERSECTION}, {@link TypeKind#MODULE}, {@link TypeKind#NONE}, {@link 390 * TypeKind#NULL}, {@link TypeKind#OTHER}, {@link TypeKind#PACKAGE}, {@link TypeKind#UNION}, {@link TypeKind#VOID}, or 391 * {@link TypeKind#WILDCARD} 392 * 393 * @return a non-{@code null} signature 394 * 395 * @see TypeKind 396 * 397 * @spec https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html#jvms-4.7.9.1 Java Virtual Machine 398 * Specification, section 4.7.9.1 399 */ 400 public static final String signature(final TypeMirror t, final Domain d) { 401 final StringBuilder sb = new StringBuilder(); 402 try (var lock = d.lock()) { 403 typeSignature(t, sb, d); 404 } 405 return sb.toString(); 406 } 407 408 private static final void typeSignature(final TypeMirror t, final StringBuilder sb, final Domain d) { 409 // Precondition: under domain lock 410 switch (t.getKind()) { 411 case ARRAY -> typeSignature(((ArrayType)t).getComponentType(), sb.append('['), d); // recursive 412 case BOOLEAN -> sb.append('Z'); 413 case BYTE -> sb.append('B'); 414 case CHAR -> sb.append('C'); 415 case DECLARED -> classTypeSignature((DeclaredType)t, sb, d); 416 case DOUBLE -> sb.append('D'); 417 case FLOAT -> sb.append('F'); 418 case INT -> sb.append('I'); 419 case LONG -> sb.append('J'); 420 case SHORT -> sb.append('S'); 421 case TYPEVAR -> sb.append('T').append(((TypeVariable)t).asElement().getSimpleName()).append(';'); 422 default -> throw new IllegalArgumentException("t: " + t); 423 } 424 } 425 426 private static final void classTypeSignature(final TypeMirror t, final StringBuilder sb, final Domain d) { 427 // Precondition: under domain lock 428 switch (t.getKind()) { 429 case NONE: 430 return; 431 case DECLARED: 432 break; 433 default: 434 throw new IllegalArgumentException("t: " + t); 435 } 436 final DeclaredType dt = (DeclaredType)t; 437 438 // Build a deque of elements from the package to the (possibly inner or nested) class. 439 final Deque<Element> dq = new ArrayDeque<>(); 440 Element e = dt.asElement(); 441 while (e != null && e.getKind() != ElementKind.MODULE) { 442 dq.push(e); 443 e = e.getEnclosingElement(); 444 } 445 446 sb.append('L'); 447 448 final Iterator<Element> i = dq.iterator(); 449 while (i.hasNext()) { 450 e = i.next(); 451 switch (e.getKind()) { 452 case PACKAGE: 453 // java.lang becomes java/lang 454 sb.append(((PackageElement)e).getQualifiedName().toString().replace('.', '/')); 455 assert i.hasNext(); 456 sb.append('/'); 457 break; 458 case ANNOTATION_TYPE: 459 case CLASS: 460 case ENUM: 461 case INTERFACE: 462 case RECORD: 463 // Outer.Inner remains Outer.Inner (i.e. not Outer$Inner or Outer/Inner) 464 sb.append(e.getSimpleName()); 465 if (i.hasNext()) { 466 sb.append('.'); 467 } 468 break; 469 default: 470 // note that a method could fall in here; we just skip it 471 break; 472 } 473 i.remove(); 474 } 475 assert dq.isEmpty(); 476 477 // Now for the type arguments 478 final List<? extends TypeMirror> typeArguments = dt.getTypeArguments(); 479 if (!typeArguments.isEmpty()) { 480 sb.append('<'); 481 for (final TypeMirror ta : typeArguments) { 482 switch (ta.getKind()) { 483 case WILDCARD: 484 final WildcardType w = (WildcardType)ta; 485 final TypeMirror superBound = w.getSuperBound(); 486 if (superBound == null) { 487 final TypeMirror extendsBound = w.getExtendsBound(); 488 if (extendsBound == null) { 489 sb.append('*'); // I guess? 490 } else { 491 sb.append('+'); 492 typeSignature(extendsBound, sb, d); 493 } 494 } else { 495 sb.append('-'); 496 typeSignature(superBound, sb, d); 497 } 498 break; 499 default: 500 typeSignature(ta, sb, d); 501 break; 502 } 503 } 504 sb.append('>'); 505 } 506 507 sb.append(';'); 508 } 509 510}