All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.lang.runtime.SwitchBootstraps Maven / Gradle / Ivy

There is a newer version: 17.alpha.0.57
Show newest version
/*
 * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.lang.runtime;

import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantBootstraps;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.stream.Stream;

import jdk.internal.javac.PreviewFeature;

import static java.util.Objects.requireNonNull;

/**
 * Bootstrap methods for linking {@code invokedynamic} call sites that implement
 * the selection functionality of the {@code switch} statement.  The bootstraps
 * take additional static arguments corresponding to the {@code case} labels
 * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}.
 *
 * @since 17
 */
@PreviewFeature(feature=PreviewFeature.Feature.SWITCH_PATTERN_MATCHING)
public class SwitchBootstraps {

    private SwitchBootstraps() {}

    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();

    private static final MethodHandle DO_TYPE_SWITCH;
    private static final MethodHandle DO_ENUM_SWITCH;

    static {
        try {
            DO_TYPE_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doTypeSwitch",
                                           MethodType.methodType(int.class, Object.class, int.class, Object[].class));
            DO_ENUM_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doEnumSwitch",
                                           MethodType.methodType(int.class, Enum.class, int.class, Object[].class));
        }
        catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * Bootstrap method for linking an {@code invokedynamic} call site that
     * implements a {@code switch} on a target of a reference type.  The static
     * arguments are an array of case labels which must be non-null and of type
     * {@code String} or {@code Integer} or {@code Class}.
     * 

* The type of the returned {@code CallSite}'s method handle will have * a return type of {@code int}. It has two parameters: the first argument * will be an {@code Object} instance ({@code target}) and the second * will be {@code int} ({@code restart}). *

* If the {@code target} is {@code null}, then the method of the call site * returns {@literal -1}. *

* If the {@code target} is not {@code null}, then the method of the call site * returns the index of the first element in the {@code labels} array starting from * the {@code restart} index matching one of the following conditions: *

    *
  • the element is of type {@code Class} that is assignable * from the target's class; or
  • *
  • the element is of type {@code String} or {@code Integer} and * equals to the target.
  • *
*

* If no element in the {@code labels} array matches the target, then * the method of the call site return the length of the {@code labels} array. * * @param lookup Represents a lookup context with the accessibility * privileges of the caller. When used with {@code invokedynamic}, * this is stacked automatically by the VM. * @param invocationName unused * @param invocationType The invocation type of the {@code CallSite} with two parameters, * a reference type, an {@code int}, and {@code int} as a return type. * @param labels case labels - {@code String} and {@code Integer} constants * and {@code Class} instances, in any combination * @return a {@code CallSite} returning the first matching element as described above * * @throws NullPointerException if any argument is {@code null} * @throws IllegalArgumentException if any element in the labels array is null, if the * invocation type is not not a method type of first parameter of a reference type, * second parameter of type {@code int} and with {@code int} as its return type, * or if {@code labels} contains an element that is not of type {@code String}, * {@code Integer} or {@code Class}. * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures */ public static CallSite typeSwitch(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType, Object... labels) { if (invocationType.parameterCount() != 2 || (!invocationType.returnType().equals(int.class)) || invocationType.parameterType(0).isPrimitive() || !invocationType.parameterType(1).equals(int.class)) throw new IllegalArgumentException("Illegal invocation type " + invocationType); requireNonNull(labels); labels = labels.clone(); Stream.of(labels).forEach(SwitchBootstraps::verifyLabel); MethodHandle target = MethodHandles.insertArguments(DO_TYPE_SWITCH, 2, (Object) labels); return new ConstantCallSite(target); } private static void verifyLabel(Object label) { if (label == null) { throw new IllegalArgumentException("null label found"); } Class labelClass = label.getClass(); if (labelClass != Class.class && labelClass != String.class && labelClass != Integer.class) { throw new IllegalArgumentException("label with illegal type found: " + label.getClass()); } } private static int doTypeSwitch(Object target, int startIndex, Object[] labels) { if (target == null) return -1; // Dumbest possible strategy Class targetClass = target.getClass(); for (int i = startIndex; i < labels.length; i++) { Object label = labels[i]; if (label instanceof Class c) { if (c.isAssignableFrom(targetClass)) return i; } else if (label instanceof Integer constant) { if (target instanceof Number input && constant.intValue() == input.intValue()) { return i; } else if (target instanceof Character input && constant.intValue() == input.charValue()) { return i; } } else if (label.equals(target)) { return i; } } return labels.length; } /** * Bootstrap method for linking an {@code invokedynamic} call site that * implements a {@code switch} on a target of an enum type. The static * arguments are used to encode the case labels associated to the switch * construct, where each label can be encoded in two ways: *

    *
  • as a {@code String} value, which represents the name of * the enum constant associated with the label
  • *
  • as a {@code Class} value, which represents the enum type * associated with a type test pattern
  • *
*

* The returned {@code CallSite}'s method handle will have * a return type of {@code int} and accepts two parameters: the first argument * will be an {@code Enum} instance ({@code target}) and the second * will be {@code int} ({@code restart}). *

* If the {@code target} is {@code null}, then the method of the call site * returns {@literal -1}. *

* If the {@code target} is not {@code null}, then the method of the call site * returns the index of the first element in the {@code labels} array starting from * the {@code restart} index matching one of the following conditions: *

    *
  • the element is of type {@code Class} that is assignable * from the target's class; or
  • *
  • the element is of type {@code String} and equals to the target * enum constant's {@link Enum#name()}.
  • *
*

* If no element in the {@code labels} array matches the target, then * the method of the call site return the length of the {@code labels} array. * * @param lookup Represents a lookup context with the accessibility * privileges of the caller. When used with {@code invokedynamic}, * this is stacked automatically by the VM. * @param invocationName unused * @param invocationType The invocation type of the {@code CallSite} with two parameters, * an enum type, an {@code int}, and {@code int} as a return type. * @param labels case labels - {@code String} constants and {@code Class} instances, * in any combination * @return a {@code CallSite} returning the first matching element as described above * * @throws NullPointerException if any argument is {@code null} * @throws IllegalArgumentException if any element in the labels array is null, if the * invocation type is not a method type whose first parameter type is an enum type, * second parameter of type {@code int} and whose return type is {@code int}, * or if {@code labels} contains an element that is not of type {@code String} or * {@code Class} of the target enum type. * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures */ public static CallSite enumSwitch(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType, Object... labels) { if (invocationType.parameterCount() != 2 || (!invocationType.returnType().equals(int.class)) || invocationType.parameterType(0).isPrimitive() || !invocationType.parameterType(0).isEnum() || !invocationType.parameterType(1).equals(int.class)) throw new IllegalArgumentException("Illegal invocation type " + invocationType); requireNonNull(labels); labels = labels.clone(); Class enumClass = invocationType.parameterType(0); labels = Stream.of(labels).map(l -> convertEnumConstants(lookup, enumClass, l)).toArray(); MethodHandle target = MethodHandles.insertArguments(DO_ENUM_SWITCH, 2, (Object) labels); target = target.asType(invocationType); return new ConstantCallSite(target); } private static > Object convertEnumConstants(MethodHandles.Lookup lookup, Class enumClassTemplate, Object label) { if (label == null) { throw new IllegalArgumentException("null label found"); } Class labelClass = label.getClass(); if (labelClass == Class.class) { if (label != enumClassTemplate) { throw new IllegalArgumentException("the Class label: " + label + ", expected the provided enum class: " + enumClassTemplate); } return label; } else if (labelClass == String.class) { @SuppressWarnings("unchecked") Class enumClass = (Class) enumClassTemplate; try { return ConstantBootstraps.enumConstant(lookup, (String) label, enumClass); } catch (IllegalArgumentException ex) { return null; } } else { throw new IllegalArgumentException("label with illegal type found: " + labelClass + ", expected label of type either String or Class"); } } private static int doEnumSwitch(Enum target, int startIndex, Object[] labels) { if (target == null) return -1; // Dumbest possible strategy Class targetClass = target.getClass(); for (int i = startIndex; i < labels.length; i++) { Object label = labels[i]; if (label instanceof Class c) { if (c.isAssignableFrom(targetClass)) return i; } else if (label == target) { return i; } } return labels.length; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy