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

java.lang.invoke.StringConcatFactory Maven / Gradle / Ivy

There is a newer version: 17.alpha.0.57
Show newest version
/*
 * Copyright (c) 2015, 2020, 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.invoke;

import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.vm.annotation.Stable;
import sun.invoke.util.Wrapper;

import java.lang.invoke.MethodHandles.Lookup;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

import static java.lang.invoke.MethodType.methodType;

/**
 * 

Methods to facilitate the creation of String concatenation methods, that * can be used to efficiently concatenate a known number of arguments of known * types, possibly after type adaptation and partial evaluation of arguments. * These methods are typically used as bootstrap methods for {@code * invokedynamic} call sites, to support the string concatenation * feature of the Java Programming Language. * *

Indirect access to the behavior specified by the provided {@code * MethodHandle} proceeds in order through two phases: * *

    *
  1. Linkage occurs when the methods in this class are invoked. * They take as arguments a method type describing the concatenated arguments * count and types, and optionally the String recipe, plus the * constants that participate in the String concatenation. The details on * accepted recipe shapes are described further below. Linkage may involve * dynamically loading a new class that implements the expected concatenation * behavior. The {@code CallSite} holds the {@code MethodHandle} pointing to the * exact concatenation method. The concatenation methods may be shared among * different {@code CallSite}s, e.g. if linkage methods produce them as pure * functions.
  2. * *
  3. Invocation occurs when a generated concatenation method is * invoked with the exact dynamic arguments. This may occur many times for a * single concatenation method. The method referenced by the behavior {@code * MethodHandle} is invoked with the static arguments and any additional dynamic * arguments provided on invocation, as if by {@link MethodHandle#invoke(Object...)}.
  4. *
* *

This class provides two forms of linkage methods: a simple version * ({@link #makeConcat(java.lang.invoke.MethodHandles.Lookup, String, * MethodType)}) using only the dynamic arguments, and an advanced version * ({@link #makeConcatWithConstants(java.lang.invoke.MethodHandles.Lookup, * String, MethodType, String, Object...)} using the advanced forms of capturing * the constant arguments. The advanced strategy can produce marginally better * invocation bytecode, at the expense of exploding the number of shapes of * string concatenation methods present at runtime, because those shapes would * include constant static arguments as well. * * @author Aleksey Shipilev * @author Remi Forax * @author Peter Levart * * @apiNote *

There is a JVM limit (classfile structural constraint): no method * can call with more than 255 slots. This limits the number of static and * dynamic arguments one can pass to bootstrap method. Since there are potential * concatenation strategies that use {@code MethodHandle} combinators, we need * to reserve a few empty slots on the parameter lists to capture the * temporal results. This is why bootstrap methods in this factory do not accept * more than 200 argument slots. Users requiring more than 200 argument slots in * concatenation are expected to split the large concatenation in smaller * expressions. * * @since 9 */ public final class StringConcatFactory { /** * Tag used to demarcate an ordinary argument. */ private static final char TAG_ARG = '\u0001'; /** * Tag used to demarcate a constant. */ private static final char TAG_CONST = '\u0002'; /** * Maximum number of argument slots in String Concat call. * * While the maximum number of argument slots that indy call can handle is 253, * we do not use all those slots, to let the strategies with MethodHandle * combinators to use some arguments. */ private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200; private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); // StringConcatFactory bootstrap methods are startup sensitive, and may be // special cased in java.lang.invoke.BootstrapMethodInvoker to ensure // methods are invoked with exact type information to avoid generating // code for runtime checks. Take care any changes or additions here are // reflected there as appropriate. /** * Facilitates the creation of optimized String concatenation methods, that * can be used to efficiently concatenate a known number of arguments of * known types, possibly after type adaptation and partial evaluation of * arguments. Typically used as a bootstrap method for {@code * invokedynamic} call sites, to support the string concatenation * feature of the Java Programming Language. * *

When the target of the {@code CallSite} returned from this method is * invoked, it returns the result of String concatenation, taking all * function arguments passed to the linkage method as inputs for * concatenation. The target signature is given by {@code concatType}. * For a target accepting: *

    *
  • zero inputs, concatenation results in an empty string;
  • *
  • one input, concatenation results in the single * input converted as per JLS 5.1.11 "String Conversion"; otherwise
  • *
  • two or more inputs, the inputs are concatenated as per * requirements stated in JLS 15.18.1 "String Concatenation Operator +". * The inputs are converted as per JLS 5.1.11 "String Conversion", * and combined from left to right.
  • *
* *

Assume the linkage arguments are as follows: * *

    *
  • {@code concatType}, describing the {@code CallSite} signature
  • *
* *

Then the following linkage invariants must hold: * *

    *
  • The number of parameter slots in {@code concatType} is * less than or equal to 200
  • *
  • The return type in {@code concatType} is assignable from {@link java.lang.String}
  • *
* * @param lookup Represents a lookup context with the accessibility * privileges of the caller. Specifically, the lookup * context must have * {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess() * full privilege access}. * When used with {@code invokedynamic}, this is stacked * automatically by the VM. * @param name The name of the method to implement. This name is * arbitrary, and has no meaning for this linkage method. * When used with {@code invokedynamic}, this is provided by * the {@code NameAndType} of the {@code InvokeDynamic} * structure and is stacked automatically by the VM. * @param concatType The expected signature of the {@code CallSite}. The * parameter types represent the types of concatenation * arguments; the return type is always assignable from {@link * java.lang.String}. When used with {@code invokedynamic}, * this is provided by the {@code NameAndType} of the {@code * InvokeDynamic} structure and is stacked automatically by * the VM. * @return a CallSite whose target can be used to perform String * concatenation, with dynamic concatenation arguments described by the given * {@code concatType}. * @throws StringConcatException If any of the linkage invariants described * here are violated, or the lookup context * does not have private access privileges. * @throws NullPointerException If any of the incoming arguments is null. * This will never happen when a bootstrap method * is called with invokedynamic. * * @jls 5.1.11 String Conversion * @jls 15.18.1 String Concatenation Operator + */ public static CallSite makeConcat(MethodHandles.Lookup lookup, String name, MethodType concatType) throws StringConcatException { // This bootstrap method is unlikely to be used in practice, // avoid optimizing it at the expense of makeConcatWithConstants // Mock the recipe to reuse the concat generator code String recipe = "\u0001".repeat(concatType.parameterCount()); return makeConcatWithConstants(lookup, name, concatType, recipe); } /** * Facilitates the creation of optimized String concatenation methods, that * can be used to efficiently concatenate a known number of arguments of * known types, possibly after type adaptation and partial evaluation of * arguments. Typically used as a bootstrap method for {@code * invokedynamic} call sites, to support the string concatenation * feature of the Java Programming Language. * *

When the target of the {@code CallSite} returned from this method is * invoked, it returns the result of String concatenation, taking all * function arguments and constants passed to the linkage method as inputs for * concatenation. The target signature is given by {@code concatType}, and * does not include constants. * For a target accepting: *

    *
  • zero inputs, concatenation results in an empty string;
  • *
  • one input, concatenation results in the single * input converted as per JLS 5.1.11 "String Conversion"; otherwise
  • *
  • two or more inputs, the inputs are concatenated as per * requirements stated in JLS 15.18.1 "String Concatenation Operator +". * The inputs are converted as per JLS 5.1.11 "String Conversion", * and combined from left to right.
  • *
* *

The concatenation recipe is a String description for the way to * construct a concatenated String from the arguments and constants. The * recipe is processed from left to right, and each character represents an * input to concatenation. Recipe characters mean: * *

    * *
  • \1 (Unicode point 0001): an ordinary argument. This * input is passed through dynamic argument, and is provided during the * concatenation method invocation. This input can be null.
  • * *
  • \2 (Unicode point 0002): a constant. This input passed * through static bootstrap argument. This constant can be any value * representable in constant pool. If necessary, the factory would call * {@code toString} to perform a one-time String conversion.
  • * *
  • Any other char value: a single character constant.
  • *
* *

Assume the linkage arguments are as follows: * *

    *
  • {@code concatType}, describing the {@code CallSite} signature
  • *
  • {@code recipe}, describing the String recipe
  • *
  • {@code constants}, the vararg array of constants
  • *
* *

Then the following linkage invariants must hold: * *

    *
  • The number of parameter slots in {@code concatType} is less than * or equal to 200
  • * *
  • The parameter count in {@code concatType} is equal to number of \1 tags * in {@code recipe}
  • * *
  • The return type in {@code concatType} is assignable * from {@link java.lang.String}, and matches the return type of the * returned {@link MethodHandle}
  • * *
  • The number of elements in {@code constants} is equal to number of \2 * tags in {@code recipe}
  • *
* * @param lookup Represents a lookup context with the accessibility * privileges of the caller. Specifically, the lookup * context must have * {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess() * full privilege access}. * When used with {@code invokedynamic}, this is stacked * automatically by the VM. * @param name The name of the method to implement. This name is * arbitrary, and has no meaning for this linkage method. * When used with {@code invokedynamic}, this is provided * by the {@code NameAndType} of the {@code InvokeDynamic} * structure and is stacked automatically by the VM. * @param concatType The expected signature of the {@code CallSite}. The * parameter types represent the types of dynamic concatenation * arguments; the return type is always assignable from {@link * java.lang.String}. When used with {@code * invokedynamic}, this is provided by the {@code * NameAndType} of the {@code InvokeDynamic} structure and * is stacked automatically by the VM. * @param recipe Concatenation recipe, described above. * @param constants A vararg parameter representing the constants passed to * the linkage method. * @return a CallSite whose target can be used to perform String * concatenation, with dynamic concatenation arguments described by the given * {@code concatType}. * @throws StringConcatException If any of the linkage invariants described * here are violated, or the lookup context * does not have private access privileges. * @throws NullPointerException If any of the incoming arguments is null, or * any constant in {@code recipe} is null. * This will never happen when a bootstrap method * is called with invokedynamic. * @apiNote Code generators have three distinct ways to process a constant * string operand S in a string concatenation expression. First, S can be * materialized as a reference (using ldc) and passed as an ordinary argument * (recipe '\1'). Or, S can be stored in the constant pool and passed as a * constant (recipe '\2') . Finally, if S contains neither of the recipe * tag characters ('\1', '\2') then S can be interpolated into the recipe * itself, causing its characters to be inserted into the result. * * @jls 5.1.11 String Conversion * @jls 15.18.1 String Concatenation Operator + */ public static CallSite makeConcatWithConstants(MethodHandles.Lookup lookup, String name, MethodType concatType, String recipe, Object... constants) throws StringConcatException { Objects.requireNonNull(lookup, "Lookup is null"); Objects.requireNonNull(name, "Name is null"); Objects.requireNonNull(concatType, "Concat type is null"); Objects.requireNonNull(constants, "Constants are null"); for (Object o : constants) { Objects.requireNonNull(o, "Cannot accept null constants"); } if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { throw new StringConcatException("Invalid caller: " + lookup.lookupClass().getName()); } List elements = parseRecipe(concatType, recipe, constants); if (!concatType.returnType().isAssignableFrom(String.class)) { throw new StringConcatException( "The return type should be compatible with String, but it is " + concatType.returnType()); } if (concatType.parameterSlotCount() > MAX_INDY_CONCAT_ARG_SLOTS) { throw new StringConcatException("Too many concat argument slots: " + concatType.parameterSlotCount() + ", can only accept " + MAX_INDY_CONCAT_ARG_SLOTS); } try { return new ConstantCallSite( generateMHInlineCopy(concatType, elements) .viewAsType(concatType, true)); } catch (Error e) { // Pass through any error throw e; } catch (Throwable t) { throw new StringConcatException("Generator failed", t); } } private static List parseRecipe(MethodType concatType, String recipe, Object[] constants) throws StringConcatException { Objects.requireNonNull(recipe, "Recipe is null"); // Element list containing String constants, or null for arguments List elements = new ArrayList<>(); int cCount = 0; int oCount = 0; StringBuilder acc = new StringBuilder(); for (int i = 0; i < recipe.length(); i++) { char c = recipe.charAt(i); if (c == TAG_CONST) { if (cCount == constants.length) { // Not enough constants throw constantMismatch(constants, cCount); } // Accumulate constant args along with any constants encoded // into the recipe acc.append(constants[cCount++]); } else if (c == TAG_ARG) { // Flush any accumulated characters into a constant if (acc.length() > 0) { elements.add(acc.toString()); acc.setLength(0); } elements.add(null); oCount++; } else { // Not a special character, this is a constant embedded into // the recipe itself. acc.append(c); } } // Flush the remaining characters as constant: if (acc.length() > 0) { elements.add(acc.toString()); } if (oCount != concatType.parameterCount()) { throw argumentMismatch(concatType, oCount); } if (cCount < constants.length) { throw constantMismatch(constants, cCount); } return elements; } private static StringConcatException argumentMismatch(MethodType concatType, int oCount) { return new StringConcatException( "Mismatched number of concat arguments: recipe wants " + oCount + " arguments, but signature provides " + concatType.parameterCount()); } private static StringConcatException constantMismatch(Object[] constants, int cCount) { return new StringConcatException( "Mismatched number of concat constants: recipe wants " + cCount + " constants, but only " + constants.length + " are passed"); } /** *

This strategy replicates what StringBuilders are doing: it builds the * byte[] array on its own and passes that byte[] array to String * constructor. This strategy requires access to some private APIs in JDK, * most notably, the private String constructor that accepts byte[] arrays * without copying. */ private static MethodHandle generateMHInlineCopy(MethodType mt, List elements) { // Fast-path unary concatenations if (elements.size() == 1) { String s0 = elements.get(0); if (s0 == null) { return unaryConcat(mt.parameterType(0)); } else { return MethodHandles.insertArguments(unaryConcat(Object.class), 0, s0); } } // Fast-path binary concatenations if (elements.size() == 2) { // Two arguments String s0 = elements.get(0); String s1 = elements.get(1); if (mt.parameterCount() == 2 && !mt.parameterType(0).isPrimitive() && !mt.parameterType(1).isPrimitive() && s0 == null && s1 == null) { return simpleConcat(); } else if (mt.parameterCount() == 1) { // One argument, one constant String constant; int constIdx; if (s1 == null) { constant = s0; constIdx = 0; } else { constant = s1; constIdx = 1; } if (constant.isEmpty()) { return unaryConcat(mt.parameterType(0)); } else if (!mt.parameterType(0).isPrimitive()) { // Non-primitive argument return MethodHandles.insertArguments(simpleConcat(), constIdx, constant); } } // else... fall-through to slow-path } // Create filters and obtain filtered parameter types. Filters would be used in the beginning // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings). // The filtered argument type list is used all over in the combinators below. Class[] ptypes = mt.erase().parameterArray(); MethodHandle[] filters = null; for (int i = 0; i < ptypes.length; i++) { MethodHandle filter = stringifierFor(ptypes[i]); if (filter != null) { if (filters == null) { filters = new MethodHandle[ptypes.length]; } filters[i] = filter; ptypes[i] = String.class; } } // Start building the combinator tree. The tree "starts" with ()String, and "finishes" // with the (byte[], long)String shape to invoke newString in StringConcatHelper. The combinators are // assembled bottom-up, which makes the code arguably hard to read. // Drop all remaining parameter types, leave only helper arguments: MethodHandle mh = MethodHandles.dropArguments(newString(), 2, ptypes); long initialLengthCoder = INITIAL_CODER; // Mix in prependers. This happens when (byte[], long) = (storage, indexCoder) is already // known from the combinators below. We are assembling the string backwards, so the index coded // into indexCoder is the *ending* index. // We need one prepender per argument, but also need to fold in constants. We do so by greedily // create prependers that fold in surrounding constants into the argument prepender. This reduces // the number of unique MH combinator tree shapes we'll create in an application. String constant = null; int pos = 0; for (String el : elements) { // Do the prepend, and put "new" index at index 1 if (el != null) { // Constant element // Eagerly update the initialLengthCoder value initialLengthCoder = JLA.stringConcatMix(initialLengthCoder, el); // Save the constant and fold it either into the next // argument prepender, or into the newArray combinator assert (constant == null); constant = el; } else { // Add prepender, along with any prefix constant mh = MethodHandles.filterArgumentsWithCombiner( mh, 1, prepender(constant, ptypes[pos]), 1, 0, // indexCoder, storage 2 + pos // selected argument ); constant = null; pos++; } } // Fold in byte[] instantiation at argument 0 MethodHandle newArrayCombinator; if (constant != null) { // newArray variant that deals with prepending the trailing constant // // initialLengthCoder has been adjusted to have the correct coder // and length already, but to avoid binding an extra variable to // the method handle we now adjust the length to be correct for the // first prepender above, while adjusting for the missing length of // the constant in StringConcatHelper initialLengthCoder -= constant.length(); newArrayCombinator = newArrayWithSuffix(constant); } else { newArrayCombinator = newArray(); } mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, newArrayCombinator, 1 // index ); // Start combining length and coder mixers. // // Length is easy: constant lengths can be computed on the spot, and all non-constant // shapes have been either converted to Strings, or explicit methods for getting the // string length out of primitives are provided. // // Coders are more interesting. Only Object, String and char arguments (and constants) // can have non-Latin1 encoding. It is easier to blindly convert constants to String, // and deduce the coder from there. Arguments would be either converted to Strings // during the initial filtering, or handled by specializations in MIXERS. // // The method handle shape before all mixers are combined in is: // (long, )String = ("indexCoder", ) // // We will bind the initialLengthCoder value to the last mixer (the one that will be // executed first), then fold that in. This leaves the shape after all mixers are // combined in as: // ()String = () pos = -1; MethodHandle mix = null; for (String el : elements) { // Constants already handled in the code above if (el == null) { if (pos >= 0) { // Compute new "index" in-place using old value plus the appropriate argument. mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, mix, 0, // old-index 1 + pos // selected argument ); } Class argClass = ptypes[++pos]; mix = mixer(argClass); } } // Insert the initialLengthCoder value into the final mixer, then // fold that into the base method handle if (pos >= 0) { mix = MethodHandles.insertArguments(mix, 0, initialLengthCoder); mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, 1 + pos // selected argument ); } else { // No mixer (constants only concat), insert initialLengthCoder directly mh = MethodHandles.insertArguments(mh, 0, initialLengthCoder); } // The method handle shape here is (). // Apply filters, converting the arguments: if (filters != null) { mh = MethodHandles.filterArguments(mh, 0, filters); } return mh; } private static MethodHandle prepender(String prefix, Class cl) { if (prefix == null) { return NULL_PREPENDERS.computeIfAbsent(cl, NULL_PREPEND); } return MethodHandles.insertArguments( PREPENDERS.computeIfAbsent(cl, PREPEND), 3, prefix); } private static MethodHandle mixer(Class cl) { return MIXERS.computeIfAbsent(cl, MIX); } // These are deliberately not lambdas to optimize startup time: private static final Function, MethodHandle> PREPEND = new Function<>() { @Override public MethodHandle apply(Class c) { MethodHandle prepend = JLA.stringConcatHelper("prepend", methodType(long.class, long.class, byte[].class, Wrapper.asPrimitiveType(c), String.class)); return prepend.rebind(); } }; private static final Function, MethodHandle> NULL_PREPEND = new Function<>() { @Override public MethodHandle apply(Class c) { return MethodHandles.insertArguments( PREPENDERS.computeIfAbsent(c, PREPEND), 3, (String)null); } }; private static final Function, MethodHandle> MIX = new Function<>() { @Override public MethodHandle apply(Class c) { MethodHandle mix = JLA.stringConcatHelper("mix", methodType(long.class, long.class, Wrapper.asPrimitiveType(c))); return mix.rebind(); } }; private @Stable static MethodHandle SIMPLE_CONCAT; private static MethodHandle simpleConcat() { MethodHandle mh = SIMPLE_CONCAT; if (mh == null) { MethodHandle simpleConcat = JLA.stringConcatHelper("simpleConcat", methodType(String.class, Object.class, Object.class)); SIMPLE_CONCAT = mh = simpleConcat.rebind(); } return mh; } private @Stable static MethodHandle NEW_STRING; private static MethodHandle newString() { MethodHandle mh = NEW_STRING; if (mh == null) { MethodHandle newString = JLA.stringConcatHelper("newString", methodType(String.class, byte[].class, long.class)); NEW_STRING = mh = newString.rebind(); } return mh; } private @Stable static MethodHandle NEW_ARRAY_SUFFIX; private static MethodHandle newArrayWithSuffix(String suffix) { MethodHandle mh = NEW_ARRAY_SUFFIX; if (mh == null) { MethodHandle newArrayWithSuffix = JLA.stringConcatHelper("newArrayWithSuffix", methodType(byte[].class, String.class, long.class)); NEW_ARRAY_SUFFIX = mh = newArrayWithSuffix.rebind(); } return MethodHandles.insertArguments(mh, 0, suffix); } private @Stable static MethodHandle NEW_ARRAY; private static MethodHandle newArray() { MethodHandle mh = NEW_ARRAY; if (mh == null) { NEW_ARRAY = mh = JLA.stringConcatHelper("newArray", methodType(byte[].class, long.class)); } return mh; } /** * Public gateways to public "stringify" methods. These methods have the * form String apply(T obj), and normally delegate to {@code String.valueOf}, * depending on argument's type. */ private @Stable static MethodHandle OBJECT_STRINGIFIER; private static MethodHandle objectStringifier() { MethodHandle mh = OBJECT_STRINGIFIER; if (mh == null) { OBJECT_STRINGIFIER = mh = JLA.stringConcatHelper("stringOf", methodType(String.class, Object.class)); } return mh; } private @Stable static MethodHandle FLOAT_STRINGIFIER; private static MethodHandle floatStringifier() { MethodHandle mh = FLOAT_STRINGIFIER; if (mh == null) { FLOAT_STRINGIFIER = mh = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, float.class); } return mh; } private @Stable static MethodHandle DOUBLE_STRINGIFIER; private static MethodHandle doubleStringifier() { MethodHandle mh = DOUBLE_STRINGIFIER; if (mh == null) { DOUBLE_STRINGIFIER = mh = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, double.class); } return mh; } private @Stable static MethodHandle INT_STRINGIFIER; private static MethodHandle intStringifier() { MethodHandle mh = INT_STRINGIFIER; if (mh == null) { INT_STRINGIFIER = mh = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, int.class); } return mh; } private @Stable static MethodHandle LONG_STRINGIFIER; private static MethodHandle longStringifier() { MethodHandle mh = LONG_STRINGIFIER; if (mh == null) { LONG_STRINGIFIER = mh = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, long.class); } return mh; } private @Stable static MethodHandle CHAR_STRINGIFIER; private static MethodHandle charStringifier() { MethodHandle mh = CHAR_STRINGIFIER; if (mh == null) { CHAR_STRINGIFIER = mh = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, char.class); } return mh; } private @Stable static MethodHandle BOOLEAN_STRINGIFIER; private static MethodHandle booleanStringifier() { MethodHandle mh = BOOLEAN_STRINGIFIER; if (mh == null) { BOOLEAN_STRINGIFIER = mh = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, boolean.class); } return mh; } private @Stable static MethodHandle NEW_STRINGIFIER; private static MethodHandle newStringifier() { MethodHandle mh = NEW_STRINGIFIER; if (mh == null) { NEW_STRINGIFIER = mh = JLA.stringConcatHelper("newStringOf", methodType(String.class, Object.class)); } return mh; } private static MethodHandle unaryConcat(Class cl) { if (!cl.isPrimitive()) { return newStringifier(); } else if (cl == int.class || cl == short.class || cl == byte.class) { return intStringifier(); } else if (cl == long.class) { return longStringifier(); } else if (cl == char.class) { return charStringifier(); } else if (cl == boolean.class) { return booleanStringifier(); } else if (cl == float.class) { return floatStringifier(); } else if (cl == double.class) { return doubleStringifier(); } else { throw new InternalError("Unhandled type for unary concatenation: " + cl); } } private static final ConcurrentMap, MethodHandle> PREPENDERS; private static final ConcurrentMap, MethodHandle> NULL_PREPENDERS; private static final ConcurrentMap, MethodHandle> MIXERS; private static final long INITIAL_CODER; static { INITIAL_CODER = JLA.stringConcatInitialCoder(); PREPENDERS = new ConcurrentHashMap<>(); NULL_PREPENDERS = new ConcurrentHashMap<>(); MIXERS = new ConcurrentHashMap<>(); } /** * Returns a stringifier for references and floats/doubles only. * Always returns null for other primitives. * * @param t class to stringify * @return stringifier; null, if not available */ private static MethodHandle stringifierFor(Class t) { if (t == Object.class) { return objectStringifier(); } else if (t == float.class) { return floatStringifier(); } else if (t == double.class) { return doubleStringifier(); } return null; } private static MethodHandle lookupStatic(Lookup lookup, Class refc, String name, Class rtype, Class... ptypes) { try { return lookup.findStatic(refc, name, MethodType.methodType(rtype, ptypes)); } catch (NoSuchMethodException | IllegalAccessException e) { throw new AssertionError(e); } } private StringConcatFactory() { // no instantiation } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy