/* * Copyright (c) 2018, 2019, 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.constant; import java.lang.Enum.EnumDesc; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle.VarHandleDesc; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; import java.util.stream.Stream; import static java.lang.constant.ConstantDescs.CD_Class; import static java.lang.constant.ConstantDescs.CD_VarHandle; import static java.lang.constant.ConstantDescs.DEFAULT_NAME; import static java.lang.constant.ConstantUtils.EMPTY_CONSTANTDESC; import static java.lang.constant.ConstantUtils.validateMemberName; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; /** * A nominal descriptor for a * dynamic constant (one described in the constant pool with * {@code Constant_Dynamic_info}.) * *

Concrete subtypes of {@linkplain DynamicConstantDesc} must be * value-based. * * @param the type of the dynamic constant * * @since 12 */ public abstract class DynamicConstantDesc implements ConstantDesc { private final DirectMethodHandleDesc bootstrapMethod; private final ConstantDesc[] bootstrapArgs; private final String constantName; private final ClassDesc constantType; private static final Map, ConstantDesc>> canonicalMap = Map.ofEntries(Map.entry(ConstantDescs.BSM_PRIMITIVE_CLASS, DynamicConstantDesc::canonicalizePrimitiveClass), Map.entry(ConstantDescs.BSM_ENUM_CONSTANT, DynamicConstantDesc::canonicalizeEnum), Map.entry(ConstantDescs.BSM_NULL_CONSTANT, DynamicConstantDesc::canonicalizeNull), Map.entry(ConstantDescs.BSM_VARHANDLE_STATIC_FIELD, DynamicConstantDesc::canonicalizeStaticFieldVarHandle), Map.entry(ConstantDescs.BSM_VARHANDLE_FIELD, DynamicConstantDesc::canonicalizeFieldVarHandle), Map.entry(ConstantDescs.BSM_VARHANDLE_ARRAY, DynamicConstantDesc::canonicalizeArrayVarHandle) ); /** * Creates a nominal descriptor for a dynamic constant. * * @param bootstrapMethod a {@link DirectMethodHandleDesc} describing the * bootstrap method for the constant * @param constantName The unqualified name that would appear in the {@code NameAndType} * operand of the {@code LDC} for this constant * @param constantType a {@link ClassDesc} describing the type * that would appear in the {@code NameAndType} operand * of the {@code LDC} for this constant * @param bootstrapArgs {@link ConstantDesc}s describing the static arguments * to the bootstrap, that would appear in the * {@code BootstrapMethods} attribute * @throws NullPointerException if any argument is null * @throws IllegalArgumentException if the {@code name} has the incorrect * format * @jvms 4.2.2 Unqualified Names */ protected DynamicConstantDesc(DirectMethodHandleDesc bootstrapMethod, String constantName, ClassDesc constantType, ConstantDesc... bootstrapArgs) { this.bootstrapMethod = requireNonNull(bootstrapMethod); this.constantName = validateMemberName(requireNonNull(constantName), true); this.constantType = requireNonNull(constantType); this.bootstrapArgs = requireNonNull(bootstrapArgs).clone(); if (constantName.length() == 0) throw new IllegalArgumentException("Illegal invocation name: " + constantName); } /** * Returns a nominal descriptor for a dynamic constant, transforming it into * a more specific type if the constant bootstrap is a well-known one and a * more specific nominal descriptor type (e.g., ClassDesc) is available. * *

Classes whose {@link Constable#describeConstable()} method produce * a {@linkplain DynamicConstantDesc} with a well-known bootstrap including * {@link Class} (for instances describing primitive types), {@link Enum}, * and {@link VarHandle}. * *

Bytecode-reading APIs that process the constant pool and wish to expose * entries as {@link ConstantDesc} to their callers should generally use this * method in preference to {@link #ofNamed(DirectMethodHandleDesc, String, ClassDesc, ConstantDesc...)} * because this may result in a more specific type that can be provided to * callers. * * @param the type of the dynamic constant * @param bootstrapMethod a {@link DirectMethodHandleDesc} describing the * bootstrap method for the constant * @param constantName The unqualified name that would appear in the {@code NameAndType} * operand of the {@code LDC} for this constant * @param constantType a {@link ClassDesc} describing the type * that would appear in the {@code NameAndType} operand * of the {@code LDC} for this constant * @param bootstrapArgs {@link ConstantDesc}s describing the static arguments * to the bootstrap, that would appear in the * {@code BootstrapMethods} attribute * @return the nominal descriptor * @throws NullPointerException if any argument is null * @throws IllegalArgumentException if the {@code name} has the incorrect * format * @jvms 4.2.2 Unqualified Names */ public static ConstantDesc ofCanonical(DirectMethodHandleDesc bootstrapMethod, String constantName, ClassDesc constantType, ConstantDesc[] bootstrapArgs) { return DynamicConstantDesc.ofNamed(bootstrapMethod, constantName, constantType, bootstrapArgs) .tryCanonicalize(); } /** * Returns a nominal descriptor for a dynamic constant. * * @param the type of the dynamic constant * @param bootstrapMethod a {@link DirectMethodHandleDesc} describing the * bootstrap method for the constant * @param constantName The unqualified name that would appear in the {@code NameAndType} * operand of the {@code LDC} for this constant * @param constantType a {@link ClassDesc} describing the type * that would appear in the {@code NameAndType} operand * of the {@code LDC} for this constant * @param bootstrapArgs {@link ConstantDesc}s describing the static arguments * to the bootstrap, that would appear in the * {@code BootstrapMethods} attribute * @return the nominal descriptor * @throws NullPointerException if any argument is null * @throws IllegalArgumentException if the {@code name} has the incorrect * format * @jvms 4.2.2 Unqualified Names */ public static DynamicConstantDesc ofNamed(DirectMethodHandleDesc bootstrapMethod, String constantName, ClassDesc constantType, ConstantDesc... bootstrapArgs) { return new AnonymousDynamicConstantDesc<>(bootstrapMethod, constantName, constantType, bootstrapArgs); } /** * Returns a nominal descriptor for a dynamic constant whose name parameter * is {@link ConstantDescs#DEFAULT_NAME}, and whose type parameter is always * the same as the bootstrap method return type. * * @param the type of the dynamic constant * @param bootstrapMethod a {@link DirectMethodHandleDesc} describing the * bootstrap method for the constant * @param bootstrapArgs {@link ConstantDesc}s describing the static arguments * to the bootstrap, that would appear in the * {@code BootstrapMethods} attribute * @return the nominal descriptor * @throws NullPointerException if any argument is null * @jvms 4.2.2 Unqualified Names */ public static DynamicConstantDesc of(DirectMethodHandleDesc bootstrapMethod, ConstantDesc... bootstrapArgs) { return ofNamed(bootstrapMethod, DEFAULT_NAME, bootstrapMethod.invocationType().returnType(), bootstrapArgs); } /** * Returns a nominal descriptor for a dynamic constant whose bootstrap has * no static arguments, whose name parameter is {@link ConstantDescs#DEFAULT_NAME}, * and whose type parameter is always the same as the bootstrap method return type. * * @param the type of the dynamic constant * @param bootstrapMethod a {@link DirectMethodHandleDesc} describing the * bootstrap method for the constant * @return the nominal descriptor * @throws NullPointerException if any argument is null */ public static DynamicConstantDesc of(DirectMethodHandleDesc bootstrapMethod) { return of(bootstrapMethod, EMPTY_CONSTANTDESC); } /** * Returns the name that would appear in the {@code NameAndType} operand * of the {@code LDC} for this constant. * * @return the constant name */ public String constantName() { return constantName; } /** * Returns a {@link ClassDesc} describing the type that would appear in the * {@code NameAndType} operand of the {@code LDC} for this constant. * * @return the constant type */ public ClassDesc constantType() { return constantType; } /** * Returns a {@link MethodHandleDesc} describing the bootstrap method for * this constant. * * @return the bootstrap method */ public DirectMethodHandleDesc bootstrapMethod() { return bootstrapMethod; } /** * Returns the bootstrap arguments for this constant. * * @return the bootstrap arguments */ public ConstantDesc[] bootstrapArgs() { return bootstrapArgs.clone(); } /** * Returns the bootstrap arguments for this constant as an immutable {@link List}. * * @return a {@link List} of the bootstrap arguments */ public List bootstrapArgsList() { return List.of(bootstrapArgs); } @SuppressWarnings("unchecked") public T resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException { try { MethodHandle bsm = (MethodHandle) bootstrapMethod.resolveConstantDesc(lookup); if (bsm.type().parameterCount() < 2 || !MethodHandles.Lookup.class.isAssignableFrom(bsm.type().parameterType(0))) { throw new BootstrapMethodError( "Invalid bootstrap method declared for resolving a dynamic constant: " + bootstrapMethod); } Object[] bsmArgs = new Object[3 + bootstrapArgs.length]; bsmArgs[0] = lookup; bsmArgs[1] = constantName; bsmArgs[2] = constantType.resolveConstantDesc(lookup); for (int i = 0; i < bootstrapArgs.length; i++) bsmArgs[3 + i] = bootstrapArgs[i].resolveConstantDesc(lookup); return (T) bsm.invokeWithArguments(bsmArgs); } catch (Error e) { throw e; } catch (Throwable t) { throw new BootstrapMethodError(t); } } private ConstantDesc tryCanonicalize() { Function, ConstantDesc> f = canonicalMap.get(bootstrapMethod); if (f != null) { try { return f.apply(this); } catch (Throwable t) { return this; } } return this; } private static ConstantDesc canonicalizeNull(DynamicConstantDesc desc) { if (desc.bootstrapArgs.length != 0) return desc; return ConstantDescs.NULL; } private static ConstantDesc canonicalizeEnum(DynamicConstantDesc desc) { if (desc.bootstrapArgs.length != 0 || desc.constantName == null) return desc; return EnumDesc.of(desc.constantType, desc.constantName); } private static ConstantDesc canonicalizePrimitiveClass(DynamicConstantDesc desc) { if (desc.bootstrapArgs.length != 0 || !desc.constantType().equals(CD_Class) || desc.constantName == null) return desc; return ClassDesc.ofDescriptor(desc.constantName); } private static ConstantDesc canonicalizeStaticFieldVarHandle(DynamicConstantDesc desc) { if (desc.bootstrapArgs.length != 2 || !desc.constantType().equals(CD_VarHandle)) return desc; return VarHandleDesc.ofStaticField((ClassDesc) desc.bootstrapArgs[0], desc.constantName, (ClassDesc) desc.bootstrapArgs[1]); } private static ConstantDesc canonicalizeFieldVarHandle(DynamicConstantDesc desc) { if (desc.bootstrapArgs.length != 2 || !desc.constantType().equals(CD_VarHandle)) return desc; return VarHandleDesc.ofField((ClassDesc) desc.bootstrapArgs[0], desc.constantName, (ClassDesc) desc.bootstrapArgs[1]); } private static ConstantDesc canonicalizeArrayVarHandle(DynamicConstantDesc desc) { if (desc.bootstrapArgs.length != 1 || !desc.constantType().equals(CD_VarHandle)) return desc; return VarHandleDesc.ofArray((ClassDesc) desc.bootstrapArgs[0]); } // @@@ To eventually support in canonicalization: DCR with BSM=MHR_METHODHANDLEDESC_ASTYPE becomes AsTypeMHDesc /** * Compares the specified object with this descriptor for equality. Returns * {@code true} if and only if the specified object is also a * {@linkplain DynamicConstantDesc}, and both descriptors have equal * bootstrap methods, bootstrap argument lists, constant name, and * constant type. * * @param o the {@code DynamicConstantDesc} to compare to this * {@code DynamicConstantDesc} * @return {@code true} if the specified {@code DynamicConstantDesc} is * equal to this {@code DynamicConstantDesc}. * */ @Override public final boolean equals(Object o) { if (this == o) return true; if (!(o instanceof DynamicConstantDesc)) return false; DynamicConstantDesc desc = (DynamicConstantDesc) o; return Objects.equals(bootstrapMethod, desc.bootstrapMethod) && Arrays.equals(bootstrapArgs, desc.bootstrapArgs) && Objects.equals(constantName, desc.constantName) && Objects.equals(constantType, desc.constantType); } @Override public final int hashCode() { int result = Objects.hash(bootstrapMethod, constantName, constantType); result = 31 * result + Arrays.hashCode(bootstrapArgs); return result; } /** * Returns a compact textual description of this constant description, * including the bootstrap method, the constant name and type, and * the static bootstrap arguments. * * @return A compact textual description of this call site descriptor */ @Override public String toString() { return String.format("DynamicConstantDesc[%s::%s(%s%s)%s]", bootstrapMethod.owner().displayName(), bootstrapMethod.methodName(), constantName.equals(ConstantDescs.DEFAULT_NAME) ? "" : constantName + "/", Stream.of(bootstrapArgs).map(Object::toString).collect(joining(",")), constantType.displayName()); } private static class AnonymousDynamicConstantDesc extends DynamicConstantDesc { AnonymousDynamicConstantDesc(DirectMethodHandleDesc bootstrapMethod, String constantName, ClassDesc constantType, ConstantDesc... bootstrapArgs) { super(bootstrapMethod, constantName, constantType, bootstrapArgs); } } }