001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2022–2023 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.constant; 015 016import java.lang.constant.ClassDesc; 017import java.lang.constant.Constable; 018import java.lang.constant.ConstantDesc; 019import java.lang.constant.DirectMethodHandleDesc; 020import java.lang.constant.DynamicConstantDesc; 021import java.lang.constant.MethodHandleDesc; 022import java.lang.constant.MethodTypeDesc; 023 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Comparator; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Optional; 031import java.util.Set; 032import java.util.SortedMap; 033import java.util.SortedSet; 034 035import java.util.function.Function; 036 037import static java.lang.constant.ConstantDescs.BSM_INVOKE; 038import static java.lang.constant.ConstantDescs.CD_Collection; 039import static java.lang.constant.ConstantDescs.CD_List; 040import static java.lang.constant.ConstantDescs.CD_Map; 041import static java.lang.constant.ConstantDescs.CD_Object; 042import static java.lang.constant.ConstantDescs.CD_Set; 043import static java.lang.constant.ConstantDescs.NULL; 044 045import static org.microbean.constant.ConstantDescs.CD_Arrays; 046import static org.microbean.constant.ConstantDescs.CD_Collections; 047import static org.microbean.constant.ConstantDescs.CD_Comparator; 048import static org.microbean.constant.ConstantDescs.CD_Entry; 049import static org.microbean.constant.ConstantDescs.CD_HashSet; 050import static org.microbean.constant.ConstantDescs.CD_Optional; 051import static org.microbean.constant.ConstantDescs.CD_SimpleImmutableEntry; 052import static org.microbean.constant.ConstantDescs.CD_SortedMap; 053import static org.microbean.constant.ConstantDescs.CD_SortedSet; 054 055/** 056 * A utility class containing {@code static} methods that can describe various things in {@link Constable} ways. 057 * 058 * @author <a href="https://about.me/lairdnelson" target="_parent">Laird Nelson</a> 059 * 060 * @see #describeConstable(Collection) 061 * 062 * @see #describeConstable(Map) 063 * 064 * @see #describeConstable(Entry) 065 * 066 * @see Constable 067 */ 068public final class Constables { 069 070 071 private static final ClassDesc CD_BootstrapMethods = ClassDesc.of("org.microbean.invoke.BootstrapMethods"); 072 073 private static final ConstantDesc[] EMPTY_CONSTANTDESC_ARRAY = new ConstantDesc[0]; 074 075 076 /* 077 * Constructors. 078 */ 079 080 081 private Constables() { 082 super(); 083 } 084 085 086 /* 087 * Static methods. 088 */ 089 090 091 @SuppressWarnings("unchecked") 092 public static final Optional<? extends ConstantDesc> describeConstable(final Object o) { 093 return 094 o == null ? Optional.of(NULL) : 095 o instanceof Constable c ? c.describeConstable() : 096 o instanceof ConstantDesc cd ? Optional.of(cd) : 097 o instanceof List<?> l ? describeConstable(l) : 098 o instanceof Set<?> s ? describeConstable(s) : 099 o instanceof Map<?, ?> m ? describeConstable(m) : 100 o instanceof Entry<?, ?> e ? describeConstable(e) : 101 o instanceof Optional<?> opt ? describeConstable(opt) : 102 Optional.empty(); 103 } 104 105 // Note that this describes the Optional itself, i.e. this is not a convenient shortcut to get to the optional's payload 106 public static final Optional<? extends ConstantDesc> describeConstable(final Optional<?> o) { 107 return describeConstable(o, Constables::describeConstable); 108 } 109 110 public static final <T> Optional<? extends ConstantDesc> describeConstable(final Optional<? extends T> o, 111 final Function<? super T, ? extends Optional<? extends ConstantDesc>> f) { 112 if (o == null) { 113 return Optional.of(NULL); 114 } else if (o.isEmpty()) { 115 return 116 Optional.of(callStatic(CD_Optional, 117 "empty", 118 MethodTypeDesc.of(CD_Optional))); 119 } 120 final Optional<? extends ConstantDesc> payload = f == null ? describeConstable(o.orElseThrow()) : f.apply(o.orElseThrow()); 121 if (payload.isEmpty()) { 122 return Optional.empty(); 123 } 124 return 125 Optional.of(callStatic(CD_Optional, 126 "ofNullable", 127 MethodTypeDesc.of(CD_Optional, CD_Object), 128 payload.orElseThrow())); 129 } 130 131 public static final Optional<? extends ConstantDesc> describeConstable(final ConstantDesc cd) { 132 return 133 cd == null ? Optional.of(NULL) : 134 cd instanceof Constable c ? c.describeConstable() : // if something implements Constable, always give it a chance to modify its own description 135 Optional.of(cd); 136 } 137 138 public static final Optional<? extends ConstantDesc> describeConstable(final Constable c) { 139 return 140 c == null ? Optional.of(NULL) : 141 c.describeConstable(); 142 } 143 144 public static final Optional<? extends ConstantDesc> describeConstable(final Collection<?> elements) { 145 return describeConstable(elements, Constables::empty, Constables::describeConstable); 146 } 147 148 public static final <E> Optional<? extends ConstantDesc> describeConstable(final Collection<? extends E> elements, 149 final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) { 150 return describeConstable(elements, Constables::empty, f); 151 } 152 153 public static final <E> Optional<? extends ConstantDesc> 154 describeConstable(final Collection<? extends E> elements, 155 final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf, 156 final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) { 157 return 158 elements == null ? Optional.of(NULL) : 159 elements instanceof List<? extends E> l ? describeConstable0(l, CD_List, cf, f) : 160 elements instanceof Set<? extends E> s ? describeConstable0(s, CD_Set, cf, f) : 161 Optional.empty(); 162 } 163 164 public static final Optional<? extends ConstantDesc> describeConstable(final List<?> elements) { 165 return describeConstable0(elements, CD_List, Constables::empty, Constables::describeConstable); 166 } 167 168 public static final <E> Optional<? extends ConstantDesc> describeConstable(final List<? extends E> elements, 169 final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) { 170 return describeConstable0(elements, CD_List, Constables::empty, f); 171 } 172 173 public static final Optional<? extends ConstantDesc> describeConstable(final Set<?> elements) { 174 return describeConstable0(elements, CD_Set, Constables::empty, Constables::describeConstable); 175 } 176 177 public static final <E> Optional<? extends ConstantDesc> describeConstable(final Set<? extends E> elements, 178 final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) { 179 return describeConstable0(elements, CD_Set, Constables::empty, f); 180 } 181 182 public static final <E> Optional<? extends ConstantDesc> 183 describeConstable(final Set<? extends E> elements, 184 final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf, 185 final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) { 186 return describeConstable0(elements, CD_Set, cf, f); 187 } 188 189 public static final Optional<? extends ConstantDesc> describeConstable(final Map<?, ?> map) { 190 return describeConstable0(map, Constables::empty, Constables::describeConstable, Constables::describeConstable); 191 } 192 193 public static final <K, V> Optional<? extends ConstantDesc> describeConstable(final Map<? extends K, ? extends V> map, 194 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf, 195 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) { 196 return describeConstable0(map, Constables::empty, kf, vf); 197 } 198 199 public static final <K, V> Optional<? extends ConstantDesc> 200 describeConstable(final Map<? extends K, ? extends V> map, 201 final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf, 202 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf, 203 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) { 204 return describeConstable0(map, cf, kf, vf); 205 } 206 207 public static final Optional<? extends ConstantDesc> describeConstable(final Entry<?, ?> entry) { 208 return describeConstable(entry, Constables::describeConstable, Constables::describeConstable); 209 } 210 211 public static final <K, V> Optional<? extends ConstantDesc> describeConstable(final Entry<? extends K, ? extends V> entry, 212 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf, 213 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) { 214 return 215 entry == null ? Optional.of(NULL) : 216 entry instanceof Constable c ? c.describeConstable() : 217 describeConstable(entry.getKey(), entry.getValue(), kf, vf); 218 } 219 220 public static final <K, V> Optional<? extends ConstantDesc> 221 describeConstable(final K k, 222 final V v, 223 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf, 224 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) { 225 final Optional<? extends ConstantDesc> key = 226 k instanceof Constable c ? c.describeConstable() : kf == null ? describeConstable(k) : kf.apply(k); 227 if (key.isPresent()) { 228 final Optional<? extends ConstantDesc> value = 229 v instanceof Constable c ? c.describeConstable() : vf == null ? describeConstable(v) : vf.apply(v); 230 if (value.isPresent()) { 231 final ConstantDesc keyDesc = key.orElseThrow(); 232 final ConstantDesc valueDesc = value.orElseThrow(); 233 if (keyDesc == NULL || valueDesc == NULL) { 234 return 235 Optional.of(construct(CD_SimpleImmutableEntry, 236 new ClassDesc[] { CD_Object, CD_Object },// K and V erasures 237 keyDesc, 238 valueDesc)); 239 } 240 // Map#entry(K, V) 241 return 242 Optional.of(callInterfaceStatic(CD_Map, 243 "entry", 244 MethodTypeDesc.of(CD_Entry, 245 CD_Object, // K erasure 246 CD_Object), // V erasure 247 keyDesc, 248 valueDesc)); 249 } 250 } 251 return Optional.empty(); 252 } 253 254 255 /* 256 * Private static methods. 257 */ 258 259 260 private static final <E> Optional<? extends ConstantDesc> 261 describeConstable0(final SortedSet<? extends E> set, 262 final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf, 263 final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) { 264 if (set == null) { 265 return Optional.of(NULL); 266 } else if (set instanceof Constable c) { 267 return c.describeConstable(); 268 } 269 270 // If the set has a user-supplied Comparator, we need to describe it as a ConstantDesc too. 271 final ConstantDesc comparatorDesc = describeComparator(set.comparator(), cf); 272 if (comparatorDesc == null) { 273 return Optional.empty(); 274 } 275 276 if (set.isEmpty()) { 277 if (comparatorDesc == NULL) { 278 return Optional.of(callStatic(CD_Collections, "emptySortedSet", MethodTypeDesc.of(CD_SortedSet))); 279 } 280 return 281 Optional.of(callStatic(CD_BootstrapMethods, 282 "immutableSortedSetOf", 283 MethodTypeDesc.of(CD_SortedSet, CD_Comparator), 284 comparatorDesc)); 285 } 286 287 final ConstantDesc[] args = elements(set, f); 288 if (args.length <= 0) { 289 return Optional.empty(); 290 } 291 292 final ConstantDesc unsortedListDesc = asList(args); 293 294 if (comparatorDesc == NULL) { 295 return 296 Optional.of(callStatic(CD_BootstrapMethods, 297 "immutableSortedSetOf", 298 MethodTypeDesc.of(CD_SortedSet, CD_Collection), 299 unsortedListDesc)); 300 } 301 return 302 Optional.of(callStatic(CD_BootstrapMethods, 303 "immutableSortedSetOf", 304 MethodTypeDesc.of(CD_SortedSet, CD_Collection, CD_Comparator), 305 unsortedListDesc, 306 comparatorDesc)); 307 } 308 309 private static final <E> Optional<? extends ConstantDesc> 310 describeConstable0(final Collection<? extends E> elements, 311 final ClassDesc listOrSetClassDesc, 312 final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf, 313 Function<? super E, ? extends Optional<? extends ConstantDesc>> f) { 314 assert CD_List.equals(listOrSetClassDesc) || CD_Set.equals(listOrSetClassDesc) : String.valueOf(listOrSetClassDesc); 315 if (elements == null) { 316 return Optional.of(NULL); 317 } else if (elements instanceof Constable c) { 318 return c.describeConstable(); 319 } else if (elements instanceof SortedSet<? extends E> ss) { 320 return describeConstable0(ss, cf, f); 321 } else if (elements.isEmpty()) { 322 return Optional.of(callInterfaceStatic(listOrSetClassDesc, "of", listOrSetClassDesc)); 323 } 324 325 if (f == null) { 326 f = Constables::describeConstable; 327 } 328 final int elementsSize = elements.size(); 329 final ConstantDesc[] args = new ConstantDesc[elementsSize]; 330 boolean nulls = false; 331 int i = 0; 332 for (final E element : elements) { 333 final Optional<? extends ConstantDesc> arg = element instanceof Constable c ? c.describeConstable() : f.apply(element); 334 if (arg == null || arg.isEmpty()) { 335 // If there's even one thing that cannot be described, then the whole thing cannot be described. 336 return Optional.empty(); 337 } 338 if (element == null) { 339 if (!nulls) { 340 nulls = true; 341 } 342 } 343 args[i++] = arg.orElseThrow(); 344 } 345 if (nulls) { 346 final ConstantDesc cd = asList(args); 347 if (CD_List.equals(listOrSetClassDesc)) { 348 assert elements instanceof List; 349 return 350 Optional.of(callStatic(CD_Collections, 351 "unmodifiableList", 352 MethodTypeDesc.of(CD_List, CD_List), 353 cd)); 354 } 355 assert elements instanceof Set; 356 return 357 Optional.of(callStatic(CD_Collections, 358 "unmodifiableSet", 359 MethodTypeDesc.of(CD_Set, CD_Set), 360 construct(CD_HashSet, new ClassDesc[] { CD_Collection }, cd))); 361 } 362 final MethodTypeDesc ofMethodTypeDesc; 363 if (elementsSize <= 10) { 364 // List.of() and Set.of() have explicit polymorphic overrides for parameter counts of up to 10. 365 final ClassDesc[] parameterArray = new ClassDesc[elementsSize]; 366 Arrays.fill(parameterArray, CD_Object); // Object is the erasure of E 367 ofMethodTypeDesc = MethodTypeDesc.of(listOrSetClassDesc, parameterArray); 368 } else { 369 // After 10 parameters, List.of() and Set.of() fall back on varargs. 370 ofMethodTypeDesc = MethodTypeDesc.of(listOrSetClassDesc, CD_Object.arrayType()); 371 } 372 return 373 Optional.of(callInterfaceStatic(listOrSetClassDesc, 374 "of", 375 ofMethodTypeDesc, 376 args)); 377 } 378 379 private static final <K, V> Optional<? extends ConstantDesc> 380 describeConstable0(final SortedMap<? extends K, ? extends V> map, 381 final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf, 382 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf, 383 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) { 384 if (map == null) { 385 return Optional.of(NULL); 386 } else if (map instanceof Constable c) { 387 return c.describeConstable(); 388 } 389 390 // If the map has a user-supplied Comparator, we need to describe it as a ConstantDesc too. 391 final ConstantDesc comparatorDesc = describeComparator(map.comparator(), cf); 392 if (comparatorDesc == null) { 393 return Optional.empty(); 394 } 395 396 if (map.isEmpty()) { 397 if (comparatorDesc == NULL) { 398 return Optional.of(callStatic(CD_Collections, "emptySortedMap", MethodTypeDesc.of(CD_SortedMap))); 399 } 400 return 401 Optional.of(callStatic(CD_BootstrapMethods, 402 "immutableEmptySortedMap", 403 MethodTypeDesc.of(CD_SortedMap, CD_Comparator), 404 comparatorDesc)); 405 } 406 407 final ConstantDesc entriesListDesc = asList(entries(map, kf, vf, false)); 408 409 if (comparatorDesc == NULL) { 410 return 411 Optional.of(callStatic(CD_BootstrapMethods, 412 "immutableSortedMapOf", 413 MethodTypeDesc.of(CD_SortedMap, CD_Collection), 414 entriesListDesc)); 415 } 416 return 417 Optional.of(callStatic(CD_BootstrapMethods, 418 "immutableSortedMapOf", 419 MethodTypeDesc.of(CD_SortedMap, CD_Collection, CD_Comparator), 420 entriesListDesc, 421 comparatorDesc)); 422 } 423 424 private static final <K, V> Optional<? extends ConstantDesc> 425 describeConstable0(final Map<? extends K, ? extends V> map, 426 final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf, 427 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf, 428 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) { 429 if (map == null) { 430 return Optional.of(NULL); 431 } else if (map instanceof Constable c) { 432 return c.describeConstable(); 433 } else if (map instanceof SortedMap<? extends K, ? extends V> sm) { 434 return describeConstable0(sm, cf, kf, vf); 435 } else if (map.isEmpty()) { 436 // Map.of() 437 return Optional.of(callInterfaceStatic(CD_Map, "of", CD_Map)); 438 } 439 440 final ConstantDesc[] args = entries(map, kf, vf, true); 441 if (args.length <= 0) { 442 return Optional.empty(); 443 } 444 445 // Map.ofEntries(Map.Entry...) 446 return Optional.of(callInterfaceStatic(CD_Map, "ofEntries", MethodTypeDesc.of(CD_Map, CD_Entry.arrayType()), args)); 447 } 448 449 private static final <E> ConstantDesc[] elements(final Collection<? extends E> source, 450 Function<? super E, ? extends Optional<? extends ConstantDesc>> f) { 451 if (f == null) { 452 f = Constables::describeConstable; 453 } 454 final ConstantDesc[] args = new ConstantDesc[source.size()]; 455 int i = 0; 456 for (final E element : source) { 457 final Optional<? extends ConstantDesc> arg = element instanceof Constable c ? c.describeConstable() : f.apply(element); 458 if (arg == null || arg.isEmpty()) { 459 // If there's even one thing that cannot be described, then the whole thing cannot be described. 460 return EMPTY_CONSTANTDESC_ARRAY; 461 } 462 args[i++] = arg.orElseThrow(); 463 } 464 return args; 465 } 466 467 private static final <K, V> ConstantDesc[] entries(final Map<? extends K, ? extends V> map, 468 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf, 469 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf, 470 final boolean rejectNulls) { 471 if (map.isEmpty()) { 472 return EMPTY_CONSTANTDESC_ARRAY; 473 } 474 final ConstantDesc[] args = new ConstantDesc[map.size()]; 475 int i = 0; 476 for (final Entry<? extends K, ? extends V> entry : map.entrySet()) { 477 if (rejectNulls && (entry.getKey() == null || entry.getValue() == null)) { 478 return EMPTY_CONSTANTDESC_ARRAY; 479 } 480 final Optional<? extends ConstantDesc> e = describeConstable(entry, kf, vf); 481 if (e.isEmpty()) { 482 // If there's even one thing that cannot be described, then the whole thing can't be described. 483 return EMPTY_CONSTANTDESC_ARRAY; 484 } 485 args[i++] = e.orElseThrow(); 486 } 487 return args; 488 } 489 490 private static final DynamicConstantDesc<?> construct(final ClassDesc cd, final ClassDesc[] constructorParameterTypes, final ConstantDesc... args) { 491 final ConstantDesc[] newArgs = new ConstantDesc[args == null || args.length <= 0 ? 1 : args.length + 1]; 492 newArgs[0] = MethodHandleDesc.ofConstructor(cd, constructorParameterTypes); 493 if (newArgs.length > 1) { 494 System.arraycopy(args, 0, newArgs, 1, args.length); 495 } 496 return DynamicConstantDesc.of(BSM_INVOKE, newArgs); 497 } 498 499 private static final DynamicConstantDesc<?> callInterfaceStatic(final ClassDesc cd, final String name, final ClassDesc returnType) { 500 return callInterfaceStatic(cd, name, MethodTypeDesc.of(returnType)); 501 } 502 503 private static final DynamicConstantDesc<?> callInterfaceStatic(final ClassDesc cd, final String name, final MethodTypeDesc sig, final ConstantDesc... args) { 504 return call(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, cd, name, sig, args); 505 } 506 507 private static final DynamicConstantDesc<?> callStatic(final ClassDesc cd, final String name, final MethodTypeDesc sig, final ConstantDesc... args) { 508 return call(DirectMethodHandleDesc.Kind.STATIC, cd, name, sig, args); 509 } 510 511 private static final DynamicConstantDesc<?> call(final DirectMethodHandleDesc.Kind kind, 512 final ClassDesc cd, 513 final String name, 514 final MethodTypeDesc sig, 515 final ConstantDesc... args) { 516 final ConstantDesc[] newArgs = new ConstantDesc[args == null || args.length == 0 ? 1 : args.length + 1]; 517 newArgs[0] = MethodHandleDesc.ofMethod(kind, cd, name, sig); 518 if (newArgs.length > 1) { 519 System.arraycopy(args, 0, newArgs, 1, args.length); 520 } 521 return DynamicConstantDesc.of(BSM_INVOKE, newArgs); 522 } 523 524 private static final <T> Optional<? extends ConstantDesc> empty(final T ignored) { 525 return Optional.empty(); 526 } 527 528 private static final ConstantDesc describeComparator(final Comparator<?> comparator, 529 final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf) { 530 return 531 comparator == null ? NULL : 532 comparator instanceof Constable c ? c.describeConstable().orElse(null) : 533 cf == null ? null : 534 cf.apply(comparator).orElse(null); 535 } 536 537 private static final DynamicConstantDesc<?> asList(final ConstantDesc[] args) { 538 return callStatic(CD_Arrays, "asList", MethodTypeDesc.of(CD_List, CD_Object.arrayType()), args); 539 } 540 541}