1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.runtime;
  27 
  28 import java.lang.invoke.CallSite;
  29 import java.lang.invoke.ConstantCallSite;
  30 import java.lang.invoke.MethodHandle;
  31 import java.lang.invoke.MethodHandles;
  32 import java.lang.invoke.MethodType;
  33 import java.util.Arrays;
  34 import java.util.Comparator;
  35 import java.util.Map;
  36 import java.util.Objects;
  37 import java.util.Set;
  38 import java.util.concurrent.ConcurrentHashMap;
  39 import java.util.function.Function;
  40 import java.util.stream.IntStream;
  41 import java.util.stream.LongStream;
  42 import java.util.stream.Stream;
  43 
  44 import static java.util.Objects.requireNonNull;
  45 
  46 /**
  47  * Bootstrap methods for linking {@code invokedynamic} call sites that implement
  48  * the selection functionality of the {@code switch} statement.  The bootstraps
  49  * take additional static arguments corresponding to the {@code case} labels
  50  * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}.
  51  *
  52  * <p>The bootstrap call site accepts a single parameter of the type of the
  53  * operand of the {@code switch}, and return an {@code int} that is the index of
  54  * the matched {@code case} label, {@code -1} if the target is {@code null},
  55  * or {@code N} if the target is not null but matches no {@code case} label.
  56  */
  57 public class SwitchBootstraps {
  58 
  59     // Shared INIT_HOOK for all switch call sites; looks the target method up in a map
  60     private static final MethodHandle CONSTANT_INIT_HOOK;
  61     private static final MethodHandle PATTERN_INIT_HOOK;
  62     private static final MethodHandle TYPE_INIT_HOOK;
  63     private static final MethodHandle PATTERN_SWITCH_METHOD;
  64     private static final MethodHandle TYPE_SWITCH_METHOD;
  65     private static final Map<Class<?>, MethodHandle> switchMethods = new ConcurrentHashMap<>();
  66 
  67     private static final Set<Class<?>> BOOLEAN_TYPES
  68             = Set.of(boolean.class, Boolean.class);
  69     // Types that can be handled as int switches
  70     private static final Set<Class<?>> INT_TYPES
  71             = Set.of(int.class, short.class, byte.class, char.class,
  72                      Integer.class, Short.class, Byte.class, Character.class);
  73     private static final Set<Class<?>> FLOAT_TYPES
  74             = Set.of(float.class, Float.class);
  75     private static final Set<Class<?>> LONG_TYPES
  76             = Set.of(long.class, Long.class);
  77     private static final Set<Class<?>> DOUBLE_TYPES
  78             = Set.of(double.class, Double.class);
  79 
  80     private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
  81     private static final Function<Class<?>, MethodHandle> lookupSwitchMethod =
  82             new Function<>() {
  83                 @Override
  84                 public MethodHandle apply(Class<?> c) {
  85                     try {
  86                         Class<?> switchClass;
  87                         if (c == Enum.class)
  88                             switchClass = EnumSwitchCallSite.class;
  89                         else if (c == String.class)
  90                             switchClass = StringSwitchCallSite.class;
  91                         else if (BOOLEAN_TYPES.contains(c) || INT_TYPES.contains(c) ||
  92                                  FLOAT_TYPES.contains(c))
  93                             switchClass = IntSwitchCallSite.class;
  94                         else if (LONG_TYPES.contains(c) || DOUBLE_TYPES.contains(c))
  95                             switchClass = LongSwitchCallSite.class;
  96                         else if (c == Object.class)
  97                             switchClass = TypeSwitchCallSite.class;
  98                         else
  99                             throw new BootstrapMethodError("Invalid switch type: " + c);
 100 
 101                         return LOOKUP.findVirtual(switchClass, "doSwitch",
 102                                                   MethodType.methodType(int.class, c));
 103                     }
 104                     catch (ReflectiveOperationException e) {
 105                         throw new BootstrapMethodError("Invalid switch type: " + c);
 106                     }
 107                 }
 108             };
 109 
 110     static {
 111         try {
 112             CONSTANT_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "constantInitHook",
 113                                                    MethodType.methodType(MethodHandle.class, CallSite.class));
 114             PATTERN_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "patternInitHook",
 115                                                   MethodType.methodType(MethodHandle.class, CallSite.class));
 116             TYPE_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "typeInitHook",
 117                                                   MethodType.methodType(MethodHandle.class, CallSite.class));
 118             PATTERN_SWITCH_METHOD = LOOKUP.findVirtual(PatternSwitchCallSite.class, "doSwitch",
 119                                                        MethodType.methodType(PatternSwitchResult.class, Object.class));
 120             TYPE_SWITCH_METHOD = LOOKUP.findVirtual(TypeSwitchCallSite.class, "doSwitch",
 121                                                     MethodType.methodType(int.class, Object.class));
 122         }
 123         catch (ReflectiveOperationException e) {
 124             throw new ExceptionInInitializerError(e);
 125         }
 126     }
 127 
 128     private static<T extends CallSite> MethodHandle constantInitHook(T receiver) {
 129         return switchMethods.computeIfAbsent(receiver.type().parameterType(0), lookupSwitchMethod)
 130                             .bindTo(receiver);
 131     }
 132 
 133     private static<T extends CallSite> MethodHandle typeInitHook(T receiver) {
 134         return TYPE_SWITCH_METHOD.bindTo(receiver);
 135     }
 136 
 137     private static<T extends CallSite> MethodHandle patternInitHook(T receiver) {
 138         return PATTERN_SWITCH_METHOD.bindTo(receiver);
 139     }
 140 
 141     /**
 142      * Bootstrap method for linking an {@code invokedynamic} call site that
 143      * implements a {@code switch} on a {@code boolean} or {@code Boolean}.
 144      * The static arguments are a varargs array of {@code boolean} labels,
 145      *
 146      * <p>The results are undefined if the labels array contains duplicates.
 147      *
 148      * @implNote
 149      *
 150      * The implementation only enforces the requirement that the labels array
 151      * be duplicate-free if system assertions are enabled.
 152      *
 153      * @param lookup Represents a lookup context with the accessibility
 154      *               privileges of the caller.  When used with {@code invokedynamic},
 155      *               this is stacked automatically by the VM.
 156      * @param invocationName The invocation name, which is ignored.  When used with
 157      *                       {@code invokedynamic}, this is provided by the
 158      *                       {@code NameAndType} of the {@code InvokeDynamic}
 159      *                       structure and is stacked automatically by the VM.
 160      * @param invocationType The invocation type of the {@code CallSite}.  This
 161      *                       method type should have a single parameter which is
 162      *                       {@code boolean} or {@code Boolean},and return {@code int}.
 163      *                       When used with {@code invokedynamic}, this is provided by
 164      *                       the {@code NameAndType} of the {@code InvokeDynamic}
 165      *                       structure and is stacked automatically by the VM.
 166      * @param booleanLabels boolean values corresponding to the case labels of the
 167      *                  {@code switch} statement.
 168      * @return the index into {@code booleanLabels} of the target value, if the target
 169      *         matches any of the labels, {@literal -1} if the target value is
 170      *         {@code null}, or {@code booleanLabels.length} if the target value does
 171      *         not match any of the labels.
 172      * @throws NullPointerException if any required argument is null
 173      * @throws IllegalArgumentException if the invocation type is not
 174      * {@code (boolean)int} or {@code (Boolean)int}
 175      * @throws Throwable if there is any error linking the call site
 176      */
 177     public static CallSite booleanSwitch(MethodHandles.Lookup lookup,
 178                                          String invocationName,
 179                                          MethodType invocationType,
 180                                          boolean... booleanLabels) throws Throwable {
 181         if (invocationType.parameterCount() != 1
 182             || (!invocationType.returnType().equals(int.class))
 183             || (!BOOLEAN_TYPES.contains(invocationType.parameterType(0))))
 184             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 185         requireNonNull(booleanLabels);
 186 
 187         int[] intLabels = IntStream.range(0, booleanLabels.length)
 188                                    .map(i -> booleanLabels[i] ? 1 : 0)
 189                                    .toArray();
 190 
 191         assert IntStream.of(intLabels).distinct().count() == intLabels.length
 192                 : "switch labels are not distinct: " + Arrays.toString(booleanLabels);
 193 
 194         return new IntSwitchCallSite(invocationType, intLabels);
 195     }
 196 
 197     /**
 198      * Bootstrap method for linking an {@code invokedynamic} call site that
 199      * implements a {@code switch} on an {@code int}, {@code short}, {@code byte},
 200      * {@code char}, or one of their box types.  The static arguments are a
 201      * varargs array of {@code int} labels.
 202      *
 203      * <p>The results are undefined if the labels array contains duplicates.
 204      *
 205      * @implNote
 206      *
 207      * The implementation only enforces the requirement that the labels array
 208      * be duplicate-free if system assertions are enabled.
 209      *
 210      * @param lookup Represents a lookup context with the accessibility
 211      *               privileges of the caller.  When used with {@code invokedynamic},
 212      *               this is stacked automatically by the VM.
 213      * @param invocationName The invocation name, which is ignored.  When used with
 214      *                       {@code invokedynamic}, this is provided by the
 215      *                       {@code NameAndType} of the {@code InvokeDynamic}
 216      *                       structure and is stacked automatically by the VM.
 217      * @param invocationType The invocation type of the {@code CallSite}.  This
 218      *                       method type should have a single parameter which is
 219      *                       one of the 32-bit or shorter primitive types, or
 220      *                       one of their box types, and return {@code int}.  When
 221      *                       used with {@code invokedynamic}, this is provided by
 222      *                       the {@code NameAndType} of the {@code InvokeDynamic}
 223      *                       structure and is stacked automatically by the VM.
 224      * @param intLabels integral values corresponding to the case labels of the
 225      *                  {@code switch} statement.
 226      * @return the index into {@code intLabels} of the target value, if the target
 227      *         matches any of the labels, {@literal -1} if the target value is
 228      *         {@code null}, or {@code intLabels.length} if the target value does
 229      *         not match any of the labels.
 230      * @throws NullPointerException if any required argument is null
 231      * @throws IllegalArgumentException if the invocation type is not
 232      * {@code (T)int}, where {@code T} is one of the 32-bit or smaller integral
 233      * primitive types, or one of their box types
 234      * @throws Throwable if there is any error linking the call site
 235      */
 236     public static CallSite intSwitch(MethodHandles.Lookup lookup,
 237                                      String invocationName,
 238                                      MethodType invocationType,
 239                                      int... intLabels) throws Throwable {
 240         if (invocationType.parameterCount() != 1
 241             || (!invocationType.returnType().equals(int.class))
 242             || (!INT_TYPES.contains(invocationType.parameterType(0))))
 243             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 244         requireNonNull(intLabels);
 245 
 246         assert IntStream.of(intLabels).distinct().count() == intLabels.length
 247                 : "switch labels are not distinct: " + Arrays.toString(intLabels);
 248 
 249         return new IntSwitchCallSite(invocationType, intLabels);
 250     }
 251 
 252     /**
 253      * Bootstrap method for linking an {@code invokedynamic} call site that
 254      * implements a {@code switch} on an {@code float} or {@code Float}.
 255      * The static arguments are a varargs array of {@code float} labels.
 256      *
 257      * <p>The results are undefined if the labels array contains duplicates
 258      * according to {@link Float#floatToIntBits(float)}.
 259      *
 260      * @implNote
 261      *
 262      * The implementation only enforces the requirement that the labels array
 263      * be duplicate-free if system assertions are enabled.
 264      *
 265      * @param lookup Represents a lookup context with the accessibility
 266      *               privileges of the caller.  When used with {@code invokedynamic},
 267      *               this is stacked automatically by the VM.
 268      * @param invocationName The invocation name, which is ignored.  When used with
 269      *                       {@code invokedynamic}, this is provided by the
 270      *                       {@code NameAndType} of the {@code InvokeDynamic}
 271      *                       structure and is stacked automatically by the VM.
 272      * @param invocationType The invocation type of the {@code CallSite}.  This
 273      *                       method type should have a single parameter which is
 274      *                       one of the 32-bit or shorter primitive types, or
 275      *                       one of their box types, and return {@code int}.  When
 276      *                       used with {@code invokedynamic}, this is provided by
 277      *                       the {@code NameAndType} of the {@code InvokeDynamic}
 278      *                       structure and is stacked automatically by the VM.
 279      * @param floatLabels float values corresponding to the case labels of the
 280      *                    {@code switch} statement.
 281      * @return the index into {@code floatLabels} of the target value, if the target
 282      *         matches any of the labels, {@literal -1} if the target value is
 283      *         {@code null}, or {@code floatLabels.length} if the target value does
 284      *         not match any of the labels.
 285      * @throws NullPointerException if any required argument is null
 286      * @throws IllegalArgumentException if the invocation type is not
 287      * {@code (float)int} or {@code (Float)int}
 288      * @throws Throwable if there is any error linking the call site
 289      */
 290     public static CallSite floatSwitch(MethodHandles.Lookup lookup,
 291                                        String invocationName,
 292                                        MethodType invocationType,
 293                                        float... floatLabels) throws Throwable {
 294         if (invocationType.parameterCount() != 1
 295             || (!invocationType.returnType().equals(int.class))
 296             || (!FLOAT_TYPES.contains(invocationType.parameterType(0))))
 297             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 298         requireNonNull(floatLabels);
 299 
 300         int[] intLabels = new int[floatLabels.length];
 301         for (int i=0; i<floatLabels.length; i++)
 302             intLabels[i] = Float.floatToIntBits(floatLabels[i]);
 303 
 304         assert IntStream.of(intLabels).distinct().count() == intLabels.length
 305                 : "switch labels are not distinct: " + Arrays.toString(floatLabels);
 306 
 307         return new IntSwitchCallSite(invocationType, intLabels);
 308     }
 309 
 310     static class IntSwitchCallSite extends ConstantCallSite {
 311         private final int[] labels;
 312         private final int[] indexes;
 313 
 314         IntSwitchCallSite(MethodType targetType,
 315                           int[] intLabels) throws Throwable {
 316             super(targetType, CONSTANT_INIT_HOOK);
 317 
 318             // expensive way to index an array
 319             indexes = IntStream.range(0, intLabels.length)
 320                                .boxed()
 321                                .sorted(Comparator.comparingInt(a -> intLabels[a]))
 322                                .mapToInt(Integer::intValue)
 323                                .toArray();
 324             labels = new int[indexes.length];
 325             for (int i=0; i<indexes.length; i++)
 326                 labels[i] = intLabels[indexes[i]];
 327         }
 328 
 329         int doSwitch(int target) {
 330             int index = Arrays.binarySearch(labels, target);
 331             return (index >= 0) ? indexes[index] : indexes.length;
 332         }
 333 
 334         int doSwitch(boolean target) {
 335             return doSwitch(target ? 1 : 0);
 336         }
 337 
 338         int doSwitch(float target) {
 339             return doSwitch(Float.floatToIntBits(target));
 340         }
 341 
 342         int doSwitch(short target) {
 343             return doSwitch((int) target);
 344         }
 345 
 346         int doSwitch(byte target) {
 347             return doSwitch((int) target);
 348         }
 349 
 350         int doSwitch(char target) {
 351             return doSwitch((int) target);
 352         }
 353 
 354         int doSwitch(Boolean target) {
 355             return (target == null) ? -1 : doSwitch((boolean) target);
 356         }
 357 
 358         int doSwitch(Integer target) {
 359             return (target == null) ? -1 : doSwitch((int) target);
 360         }
 361 
 362         int doSwitch(Float target) {
 363             return (target == null) ? -1 : doSwitch((float) target);
 364         }
 365 
 366         int doSwitch(Short target) {
 367             return (target == null) ? -1 : doSwitch((int) target);
 368         }
 369 
 370         int doSwitch(Character target) {
 371             return (target == null) ? -1 : doSwitch((int) target);
 372         }
 373 
 374         int doSwitch(Byte target) {
 375             return (target == null) ? -1 : doSwitch((int) target);
 376         }
 377     }
 378 
 379     /**
 380      * Bootstrap method for linking an {@code invokedynamic} call site that
 381      * implements a {@code switch} on a {@code long} or {@code Long}.
 382      * The static arguments are a varargs array of {@code long} labels.
 383      *
 384      * <p>The results are undefined if the labels array contains duplicates.
 385      *
 386      * @implNote
 387      *
 388      * The implementation only enforces the requirement that the labels array
 389      * be duplicate-free if system assertions are enabled.
 390      *
 391      * @param lookup Represents a lookup context with the accessibility
 392      *               privileges of the caller.  When used with {@code invokedynamic},
 393      *               this is stacked automatically by the VM.
 394      * @param invocationName The invocation name, which is ignored.  When used with
 395      *                       {@code invokedynamic}, this is provided by the
 396      *                       {@code NameAndType} of the {@code InvokeDynamic}
 397      *                       structure and is stacked automatically by the VM.
 398      * @param invocationType The invocation type of the {@code CallSite}.  This
 399      *                       method type should have a single parameter which is
 400      *                       one of the 32-bit or shorter primitive types, or
 401      *                       one of their box types, and return {@code int}.  When
 402      *                       used with {@code invokedynamic}, this is provided by
 403      *                       the {@code NameAndType} of the {@code InvokeDynamic}
 404      *                       structure and is stacked automatically by the VM.
 405      * @param longLabels long values corresponding to the case labels of the
 406      *                  {@code switch} statement.
 407      * @return the index into {@code longLabels} of the target value, if the target
 408      *         matches any of the labels, {@literal -1} if the target value is
 409      *         {@code null}, or {@code longLabels.length} if the target value does
 410      *         not match any of the labels.
 411      * @throws NullPointerException if any required argument is null
 412      * @throws IllegalArgumentException if the invocation type is not
 413      * {@code (long)int} or {@code (Long)int}
 414      * @throws Throwable if there is any error linking the call site
 415      */
 416     public static CallSite longSwitch(MethodHandles.Lookup lookup,
 417                                       String invocationName,
 418                                       MethodType invocationType,
 419                                       long... longLabels) throws Throwable {
 420         if (invocationType.parameterCount() != 1
 421             || (!invocationType.returnType().equals(int.class))
 422             || (!LONG_TYPES.contains(invocationType.parameterType(0))))
 423             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 424         requireNonNull(longLabels);
 425 
 426         assert LongStream.of(longLabels).distinct().count() == longLabels.length
 427                 : "switch labels are not distinct: " + Arrays.toString(longLabels);
 428 
 429         return new LongSwitchCallSite(invocationType, longLabels);
 430     }
 431 
 432     /**
 433      * Bootstrap method for linking an {@code invokedynamic} call site that
 434      * implements a {@code switch} on a {@code double} or {@code Double}.
 435      * The static arguments are a varargs array of {@code double} labels.
 436      *
 437      * <p>The results are undefined if the labels array contains duplicates
 438      * according to {@link Double#doubleToLongBits(double)}.
 439      *
 440      * @implNote
 441      *
 442      * The implementation only enforces the requirement that the labels array
 443      * be duplicate-free if system assertions are enabled.
 444      *
 445      * @param lookup Represents a lookup context with the accessibility
 446      *               privileges of the caller.  When used with {@code invokedynamic},
 447      *               this is stacked automatically by the VM.
 448      * @param invocationName The invocation name, which is ignored.  When used with
 449      *                       {@code invokedynamic}, this is provided by the
 450      *                       {@code NameAndType} of the {@code InvokeDynamic}
 451      *                       structure and is stacked automatically by the VM.
 452      * @param invocationType The invocation type of the {@code CallSite}.  This
 453      *                       method type should have a single parameter which is
 454      *                       one of the 32-bit or shorter primitive types, or
 455      *                       one of their box types, and return {@code int}.  When
 456      *                       used with {@code invokedynamic}, this is provided by
 457      *                       the {@code NameAndType} of the {@code InvokeDynamic}
 458      *                       structure and is stacked automatically by the VM.
 459      * @param doubleLabels long values corresponding to the case labels of the
 460      *                  {@code switch} statement.
 461      * @return the index into {@code doubleLabels} of the target value, if the target
 462      *         matches any of the labels, {@literal -1} if the target value is
 463      *         {@code null}, or {@code doubleLabels.length} if the target value does
 464      *         not match any of the labels.
 465      * @throws NullPointerException if any required argument is null
 466      * @throws IllegalArgumentException if the invocation type is not
 467      * {@code (double)int} or {@code (Double)int}
 468      * @throws Throwable if there is any error linking the call site
 469      */
 470     public static CallSite doubleSwitch(MethodHandles.Lookup lookup,
 471                                         String invocationName,
 472                                         MethodType invocationType,
 473                                         double... doubleLabels) throws Throwable {
 474         if (invocationType.parameterCount() != 1
 475             || (!invocationType.returnType().equals(int.class))
 476             || (!DOUBLE_TYPES.contains(invocationType.parameterType(0))))
 477             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 478         requireNonNull(doubleLabels);
 479 
 480         long[] longLabels = new long[doubleLabels.length];
 481         for (int i=0; i<doubleLabels.length; i++)
 482             longLabels[i] = Double.doubleToLongBits(doubleLabels[i]);
 483 
 484         assert LongStream.of(longLabels).distinct().count() == longLabels.length
 485                 : "switch labels are not distinct: " + Arrays.toString(doubleLabels);
 486 
 487         return new LongSwitchCallSite(invocationType, longLabels);
 488     }
 489 
 490     static class LongSwitchCallSite extends ConstantCallSite {
 491         private final long[] labels;
 492         private final int[] indexes;
 493 
 494         LongSwitchCallSite(MethodType targetType,
 495                            long[] longLabels) throws Throwable {
 496             super(targetType, CONSTANT_INIT_HOOK);
 497 
 498             // expensive way to index an array
 499             indexes = IntStream.range(0, longLabels.length)
 500                                .boxed()
 501                                .sorted(Comparator.comparingLong(a -> longLabels[a]))
 502                                .mapToInt(Integer::intValue)
 503                                .toArray();
 504             labels = new long[indexes.length];
 505             for (int i=0; i<indexes.length; i++)
 506                 labels[i] = longLabels[indexes[i]];
 507         }
 508 
 509         int doSwitch(long target) {
 510             int index = Arrays.binarySearch(labels, target);
 511             return (index >= 0) ? indexes[index] : indexes.length;
 512         }
 513 
 514         int doSwitch(double target) {
 515             return doSwitch(Double.doubleToLongBits(target));
 516         }
 517 
 518         int doSwitch(Long target) {
 519             return (target == null) ? -1 : doSwitch((long) target);
 520         }
 521 
 522         int doSwitch(Double target) {
 523             return (target == null) ? -1 : doSwitch((double) target);
 524         }
 525     }
 526 
 527     /**
 528      * Bootstrap method for linking an {@code invokedynamic} call site that
 529      * implements a {@code switch} on a {@code String} target.  The static
 530      * arguments are a varargs array of {@code String} labels.
 531      *
 532      * <p>The results are undefined if the labels array contains duplicates
 533      * according to {@link String#equals(Object)}.
 534      *
 535      * @implNote
 536      *
 537      * The implementation only enforces the requirement that the labels array
 538      * be duplicate-free if system assertions are enabled.
 539      *
 540      * @param lookup Represents a lookup context with the accessibility
 541      *               privileges of the caller.  When used with {@code invokedynamic},
 542      *               this is stacked automatically by the VM.
 543      * @param invocationName The invocation name, which is ignored.  When used with
 544      *                       {@code invokedynamic}, this is provided by the
 545      *                       {@code NameAndType} of the {@code InvokeDynamic}
 546      *                       structure and is stacked automatically by the VM.
 547      * @param invocationType The invocation type of the {@code CallSite}.  This
 548      *                       method type should have a single parameter of
 549      *                       {@code String}, and return {@code int}.  When
 550      *                       used with {@code invokedynamic}, this is provided by
 551      *                       the {@code NameAndType} of the {@code InvokeDynamic}
 552      *                       structure and is stacked automatically by the VM.
 553      * @param stringLabels non-null string values corresponding to the case
 554      *                     labels of the {@code switch} statement.
 555      * @return the index into {@code labels} of the target value, if the target
 556      *         matches any of the labels, {@literal -1} if the target value is
 557      *         {@code null}, or {@code stringLabels.length} if the target value
 558      *         does not match any of the labels.
 559      * @throws NullPointerException if any required argument is null
 560      * @throws IllegalArgumentException if any labels are null, or if the
 561      * invocation type is not {@code (String)int}
 562      * @throws Throwable if there is any error linking the call site
 563      */
 564     public static CallSite stringSwitch(MethodHandles.Lookup lookup,
 565                                         String invocationName,
 566                                         MethodType invocationType,
 567                                         String... stringLabels) throws Throwable {
 568         if (invocationType.parameterCount() != 1
 569             || (!invocationType.returnType().equals(int.class))
 570             || (!invocationType.parameterType(0).equals(String.class)))
 571             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 572         requireNonNull(stringLabels);
 573         if (Stream.of(stringLabels).anyMatch(Objects::isNull))
 574             throw new IllegalArgumentException("null label found");
 575 
 576         assert Stream.of(stringLabels).distinct().count() == stringLabels.length
 577                 : "switch labels are not distinct: " + Arrays.toString(stringLabels);
 578 
 579         return new StringSwitchCallSite(invocationType, stringLabels);
 580     }
 581 
 582     static class StringSwitchCallSite extends ConstantCallSite {
 583         private static final Comparator<String> STRING_BY_HASH
 584                 = Comparator.comparingInt(Objects::hashCode);
 585 
 586         private final String[] sortedByHash;
 587         private final int[] indexes;
 588         private final boolean collisions;
 589 
 590         StringSwitchCallSite(MethodType targetType,
 591                              String[] stringLabels) throws Throwable {
 592             super(targetType, CONSTANT_INIT_HOOK);
 593 
 594             // expensive way to index an array
 595             indexes = IntStream.range(0, stringLabels.length)
 596                                .boxed()
 597                                .sorted(Comparator.comparingInt(i -> stringLabels[i].hashCode()))
 598                                .mapToInt(Integer::intValue)
 599                                .toArray();
 600             sortedByHash = new String[indexes.length];
 601             for (int i=0; i<indexes.length; i++)
 602                 sortedByHash[i] = stringLabels[indexes[i]];
 603 
 604             collisions = IntStream.range(0, sortedByHash.length-1)
 605                                   .anyMatch(i -> sortedByHash[i].hashCode() == sortedByHash[i + 1].hashCode());
 606         }
 607 
 608         int doSwitch(String target) {
 609             if (target == null)
 610                 return -1;
 611 
 612             int index = Arrays.binarySearch(sortedByHash, target, STRING_BY_HASH);
 613             if (index < 0)
 614                 return indexes.length;
 615             else if (target.equals(sortedByHash[index])) {
 616                 return indexes[index];
 617             }
 618             else if (collisions) {
 619                 int hash = target.hashCode();
 620                 while (index > 0 && sortedByHash[index-1].hashCode() == hash)
 621                     --index;
 622                 for (; index < sortedByHash.length && sortedByHash[index].hashCode() == hash; index++)
 623                     if (target.equals(sortedByHash[index]))
 624                         return indexes[index];
 625             }
 626 
 627             return indexes.length;
 628         }
 629     }
 630 
 631     /**
 632      * Bootstrap method for linking an {@code invokedynamic} call site that
 633      * implements a {@code switch} on an {@code Enum} target.  The static
 634      * arguments are the enum class, and a varargs arrays of {@code String}
 635      * that are the names of the enum constants corresponding to the
 636      * {@code case} labels.
 637      *
 638      * <p>The results are undefined if the names array contains duplicates.
 639      *
 640      * @implNote
 641      *
 642      * The implementation only enforces the requirement that the labels array
 643      * be duplicate-free if system assertions are enabled.
 644      *
 645      * @param <E> the enum type
 646      * @param lookup Represents a lookup context with the accessibility
 647      *               privileges of the caller.  When used with {@code invokedynamic},
 648      *               this is stacked automatically by the VM.
 649      * @param invocationName The invocation name, which is ignored.  When used with
 650      *                       {@code invokedynamic}, this is provided by the
 651      *                       {@code NameAndType} of the {@code InvokeDynamic}
 652      *                       structure and is stacked automatically by the VM.
 653      * @param invocationType The invocation type of the {@code CallSite}.  This
 654      *                       method type should have a single parameter of
 655      *                       {@code Enum}, and return {@code int}.  When
 656      *                       used with {@code invokedynamic}, this is provided by
 657      *                       the {@code NameAndType} of the {@code InvokeDynamic}
 658      *                       structure and is stacked automatically by the VM.
 659      * @param enumClass the enum class
 660      * @param enumNames names of the enum constants against which the target
 661      *                  should be matched
 662      * @return the index into {@code labels} of the target value, if the target
 663      *         matches any of the labels, {@literal -1} if the target value is
 664      *         {@code null}, or {@code stringLabels.length} if the target value
 665      *         does not match any of the labels.
 666      * @throws IllegalArgumentException if the specified class is not an
 667      *                                  enum class, or any label name is null,
 668      *                                  or if the invocation type is not
 669      *                                  {@code (Enum)int}
 670      * @throws NullPointerException if any required argument is null
 671      * @throws Throwable if there is any error linking the call site
 672      */
 673     public static<E extends Enum<E>> CallSite enumSwitch(MethodHandles.Lookup lookup,
 674                                                          String invocationName,
 675                                                          MethodType invocationType,
 676                                                          Class<E> enumClass,
 677                                                          String... enumNames) throws Throwable {
 678         if (invocationType.parameterCount() != 1
 679             || (!invocationType.returnType().equals(int.class))
 680             || (!invocationType.parameterType(0).equals(Enum.class)))
 681             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 682         requireNonNull(enumClass);
 683         requireNonNull(enumNames);
 684         if (!enumClass.isEnum())
 685             throw new IllegalArgumentException("not an enum class");
 686         if (Stream.of(enumNames).anyMatch(Objects::isNull))
 687             throw new IllegalArgumentException("null label found");
 688 
 689         assert Stream.of(enumNames).distinct().count() == enumNames.length
 690                 : "switch labels are not distinct: " + Arrays.toString(enumNames);
 691 
 692         return new EnumSwitchCallSite<>(invocationType, enumClass, enumNames);
 693     }
 694 
 695     static class EnumSwitchCallSite<E extends Enum<E>> extends ConstantCallSite {
 696         private final int[] ordinalMap;
 697 
 698         EnumSwitchCallSite(MethodType targetType,
 699                            Class<E> enumClass,
 700                            String... enumNames) throws Throwable {
 701             super(targetType, CONSTANT_INIT_HOOK);
 702 
 703             ordinalMap = new int[enumClass.getEnumConstants().length];
 704             Arrays.fill(ordinalMap, enumNames.length);
 705 
 706             for (int i=0; i<enumNames.length; i++) {
 707                 try {
 708                     ordinalMap[E.valueOf(enumClass, enumNames[i]).ordinal()] = i;
 709                 }
 710                 catch (Exception e) {
 711                     // allow non-existent labels, but never match them
 712                     continue;
 713                 }
 714             }
 715         }
 716 
 717         @SuppressWarnings("rawtypes")
 718         int doSwitch(Enum target) {
 719             return (target == null) ? -1 : ordinalMap[target.ordinal()];
 720         }
 721     }
 722 
 723     /**
 724      * Bootstrap method for linking an {@code invokedynamic} call site that
 725      * implements a {@code switch} on a reference-typed target.  The static
 726      * arguments are a varargs array of {@code Class} labels.
 727      *
 728      * @param lookup Represents a lookup context with the accessibility
 729      *               privileges of the caller.  When used with {@code invokedynamic},
 730      *               this is stacked automatically by the VM.
 731      * @param invocationName The invocation name, which is ignored.  When used with
 732      *                       {@code invokedynamic}, this is provided by the
 733      *                       {@code NameAndType} of the {@code InvokeDynamic}
 734      *                       structure and is stacked automatically by the VM.
 735      * @param invocationType The invocation type of the {@code CallSite}.  This
 736      *                       method type should have a single parameter of
 737      *                       a reference type, and return {@code int}.  When
 738      *                       used with {@code invokedynamic}, this is provided by
 739      *                       the {@code NameAndType} of the {@code InvokeDynamic}
 740      *                       structure and is stacked automatically by the VM.
 741      * @param types non-null {@link Class} values
 742      * @return the index into {@code labels} of the target value, if the target
 743      *         is an instance of any of the types, {@literal -1} if the target
 744      *         value is {@code null}, or {@code types.length} if the target value
 745      *         is not an instance of any of the types
 746      * @throws NullPointerException if any required argument is null
 747      * @throws IllegalArgumentException if any labels are null, or if the
 748      * invocation type is not {@code (T)int for some reference type {@code T}}
 749      * @throws Throwable if there is any error linking the call site
 750      */
 751     public static CallSite typeSwitch(MethodHandles.Lookup lookup,
 752                                       String invocationName,
 753                                       MethodType invocationType,
 754                                       Class<?>... types) throws Throwable {
 755         if (invocationType.parameterCount() != 1
 756             || (!invocationType.returnType().equals(int.class))
 757             || invocationType.parameterType(0).isPrimitive())
 758             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 759         requireNonNull(types);
 760 
 761         types = types.clone();
 762         if (Stream.of(types).anyMatch(Objects::isNull))
 763             throw new IllegalArgumentException("null label found");
 764 
 765         assert Stream.of(types).distinct().count() == types.length
 766                 : "switch labels are not distinct: " + Arrays.toString(types);
 767 
 768         return new TypeSwitchCallSite(invocationType, types);
 769     }
 770 
 771     static class TypeSwitchCallSite extends ConstantCallSite {
 772         private final Class<?>[] types;
 773 
 774         TypeSwitchCallSite(MethodType targetType,
 775                            Class<?>[] types) throws Throwable {
 776             super(targetType, TYPE_INIT_HOOK);
 777             this.types = types;
 778         }
 779 
 780         int doSwitch(Object target) {
 781             if (target == null)
 782                 return -1;
 783 
 784             // Dumbest possible strategy
 785             Class<?> targetClass = target.getClass();
 786             for (int i = 0; i < types.length; i++) {
 787                 Class<?> c = types[i];
 788                 if (c.isAssignableFrom(targetClass))
 789                     return i;
 790             }
 791 
 792             return types.length;
 793         }
 794     }
 795 
 796     /**
 797      * Result type for pattern switches
 798      */
 799     public static class PatternSwitchResult {
 800         /**
 801          * The selected index, -1 if input was null, or length if not matched
 802          */
 803         public final int index;
 804 
 805         /**
 806          * The carrier
 807          */
 808         public final Object carrier;
 809 
 810         /**
 811          * Construct a PatternSwitchResult
 812          *
 813          * @param index the index
 814          * @param carrier the carrier
 815          */
 816         public PatternSwitchResult(int index, Object carrier) {
 817             this.index = index;
 818             this.carrier = carrier;
 819         }
 820     }
 821 
 822     /**
 823      * Bootstrap for pattern switches
 824      *
 825      * @param lookup the lookup (ignored)
 826      * @param invocationName the invocation name (ignored)
 827      * @param invocationType the invocation type (must return PatternSwitchResult)
 828      * @param patterns the patterns
 829      * @return the result
 830      * @throws Throwable if something went wrong
 831      */
 832     public static CallSite patternSwitch(MethodHandles.Lookup lookup,
 833                                          String invocationName,
 834                                          MethodType invocationType,
 835                                          PatternHandle... patterns) throws Throwable {
 836         if (invocationType.parameterCount() != 1
 837             || (!invocationType.returnType().equals(PatternSwitchResult.class))
 838             || invocationType.parameterType(0).isPrimitive())
 839             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
 840         requireNonNull(patterns);
 841 
 842         patterns = patterns.clone();
 843         Class<?> targetType = invocationType.parameterType(0);
 844 
 845         for (int i = 0; i < patterns.length; i++) {
 846             PatternHandle pattern = patterns[i];
 847             if (pattern.descriptor().returnType() != targetType)
 848                 patterns[i] = PatternHandles.adaptTarget(pattern, targetType);
 849         }
 850 
 851         if (Stream.of(patterns).anyMatch(Objects::isNull))
 852             throw new IllegalArgumentException("null pattern found");
 853 
 854         return new PatternSwitchCallSite(invocationType, patterns);
 855     }
 856 
 857     static class PatternSwitchCallSite extends ConstantCallSite {
 858         private final PatternHandle[] patterns;
 859 
 860         PatternSwitchCallSite(MethodType targetType,
 861                               PatternHandle[] patterns) throws Throwable {
 862             super(targetType, PATTERN_INIT_HOOK);
 863             this.patterns = patterns;
 864         }
 865 
 866         PatternSwitchResult doSwitch(Object target) throws Throwable {
 867             if (target == null)
 868                 return new PatternSwitchResult(-1, null);
 869 
 870             // Dumbest possible strategy
 871             for (int i = 0; i < patterns.length; i++) {
 872                 PatternHandle e = patterns[i];
 873                 Object o = e.tryMatch().invoke(target);
 874                 if (o != null)
 875                     return new PatternSwitchResult(i, o);
 876             }
 877 
 878             return new PatternSwitchResult(patterns.length, null);
 879 
 880         }
 881     }
 882 }