1 /*
   2  * Copyright (c) 2015, 2015, 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.invoke;
  27 
  28 import jdk.internal.org.objectweb.asm.ClassWriter;
  29 import jdk.internal.org.objectweb.asm.Label;
  30 import jdk.internal.org.objectweb.asm.MethodVisitor;
  31 import jdk.internal.org.objectweb.asm.Opcodes;
  32 import sun.misc.Unsafe;
  33 
  34 import java.lang.invoke.MethodHandles.Lookup;
  35 import java.security.AccessController;
  36 import java.security.PrivilegedAction;
  37 import java.util.*;
  38 import java.util.concurrent.ConcurrentHashMap;
  39 import java.util.concurrent.ConcurrentMap;
  40 import java.util.function.Function;
  41 
  42 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  43 
  44 /**
  45  * <p>Methods to facilitate the creation of String concatenation methods, that
  46  * can be used to efficiently concatenate a known number of arguments of known
  47  * types, possibly after type adaptation and partial evaluation of arguments.
  48  * These methods are typically used as <em>bootstrap methods</em> for {@code
  49  * invokedynamic} call sites, to support the <em>string concatenation</em>
  50  * feature of the Java Programming Language.
  51  *
  52  * <p>Indirect access to the behavior specified by the provided {@code
  53  * MethodHandle} proceeds in order through two phases:
  54  *
  55  * <ol>
  56  *     <li><em>Linkage</em> occurs when the methods in this class are invoked.
  57  * They take as arguments a method type describing the concatenated arguments
  58  * count and types, and optionally the String <em>recipe</em>, plus the
  59  * constants that participate in the String concatenation. The details on
  60  * accepted recipe shapes are described further below. Linkage may involve
  61  * dynamically loading a new class that implements the expected concatenation
  62  * behavior. The {@code CallSite} holds the {@code MethodHandle} pointing to the
  63  * exact concatenation method. The concatenation methods may be shared among
  64  * different {@code CallSite}s, e.g. if linkage methods produce them as pure
  65  * functions.</li>
  66  *
  67  * <li><em>Invocation</em> occurs when a generated concatenation method is
  68  * invoked with the exact dynamic arguments. This may occur many times for a
  69  * single concatenation method. The method referenced by the behavior {@code
  70  * MethodHandle} is invoked with the static arguments and any additional dynamic
  71  * arguments provided on invocation, as if by {@link MethodHandle#invoke(Object...)}.</li>
  72  * </ol>
  73  *
  74  * <p> This class provides two forms of linkage methods: a simple version
  75  * ({@link #makeConcat(java.lang.invoke.MethodHandles.Lookup, String,
  76  * MethodType)}) using only the dynamic arguments, and an advanced version
  77  * ({@link #makeConcatWithConstants(java.lang.invoke.MethodHandles.Lookup,
  78  * String, MethodType, String, Object...)} using the advanced forms of capturing
  79  * the constant arguments. The advanced strategy can produce marginally better
  80  * invocation bytecode, at the expense of exploding the number of shapes of
  81  * string concatenation methods present at runtime, because those shapes would
  82  * include constant static arguments as well.
  83  *
  84  * @author Aleksey Shipilev
  85  * @author Remi Forax
  86  * @author Peter Levart
  87  *
  88  * @apiNote
  89  * <p>There is a JVM limit (classfile structural constraint): no method
  90  * can call with more than 255 slots. This limits the number of static and
  91  * dynamic arguments one can pass to bootstrap method. Since there are potential
  92  * concatenation strategies that use {@code MethodHandle} combinators, we need
  93  * to reserve a few empty slots on the parameter lists to to capture the
  94  * temporal results. This is why bootstrap methods in this factory do not accept
  95  * more than 200 argument slots. Users requiring more than 200 argument slots in
  96  * concatenation are expected to split the large concatenation in smaller
  97  * expressions.
  98  */
  99 public final class StringConcatFactory {
 100 
 101     /**
 102      * Tag used to demarcate an ordinary argument.
 103      */
 104     private static final char TAG_ARG = '\u0001';
 105 
 106     /**
 107      * Tag used to demarcate a constant.
 108      */
 109     private static final char TAG_CONST = '\u0002';
 110 
 111     /**
 112      * Maximum number of argument slots in String Concat call.
 113      *
 114      * While the maximum number of argument slots that indy call can handle is 253,
 115      * we do not use all those slots, to let the strategies with MethodHandle
 116      * combinators to use some arguments.
 117      */
 118     private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200;
 119 
 120     /**
 121      * Concatenation strategy to use. See {@link Strategy} for possible options.
 122      * This option is controllable with -Djava.lang.invoke.stringConcat JDK option.
 123      */
 124     private static final Strategy STRATEGY;
 125 
 126     /**
 127      * Default strategy to use for concatenation.
 128      */
 129     private static final Strategy DEFAULT_STRATEGY = Strategy.BC_SB_SIZED;
 130 
 131     private enum Strategy {
 132         /**
 133          * Bytecode generator, calling into {@link java.lang.StringBuilder}.
 134          */
 135         BC_SB,
 136 
 137         /**
 138          * Bytecode generator, calling into {@link java.lang.StringBuilder};
 139          * but trying to estimate the required storage.
 140          */
 141         BC_SB_SIZED,
 142 
 143         /**
 144          * Bytecode generator, calling into {@link java.lang.StringBuilder};
 145          * but computing the required storage exactly.
 146          */
 147         BC_SB_SIZED_EXACT,
 148 
 149         /**
 150          * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
 151          * This strategy also tries to estimate the required storage.
 152          */
 153         MH_SB_SIZED,
 154 
 155         /**
 156          * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
 157          * This strategy also estimate the required storage exactly.
 158          */
 159         MH_SB_SIZED_EXACT,
 160 
 161         /**
 162          * MethodHandle-based generator, that constructs its own byte[] array from
 163          * the arguments. It computes the required storage exactly.
 164          */
 165         MH_INLINE_SIZED_EXACT
 166     }
 167 
 168     /**
 169      * Enables debugging: this may print debugging messages, perform additional (non-neutral for performance)
 170      * checks, etc.
 171      */
 172     private static final boolean DEBUG;
 173 
 174     /**
 175      * Enables caching of strategy stubs. This may improve the linkage time by reusing the generated
 176      * code, at the expense of contaminating the profiles.
 177      */
 178     private static final boolean CACHE_ENABLE;
 179 
 180     private static final ConcurrentMap<Key, MethodHandle> CACHE;
 181 
 182     static {
 183         // Poke the privileged block once, taking everything we need:
 184         final Object[] values = new Object[3];
 185         AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
 186             values[0] = System.getProperty("java.lang.invoke.stringConcat");
 187             values[1] = Boolean.getBoolean("java.lang.invoke.stringConcat.cache");
 188             values[2] = Boolean.getBoolean("java.lang.invoke.stringConcat.debug");
 189             return null;
 190         });
 191 
 192         final String strategy = (String)  values[0];
 193         CACHE_ENABLE          = (Boolean) values[1];
 194         DEBUG                 = (Boolean) values[2];
 195 
 196         STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy);
 197         CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null;
 198     }
 199 
 200     private static final class Key {
 201         final MethodType mt;
 202         final Recipe recipe;
 203 
 204         public Key(MethodType mt, Recipe recipe) {
 205             this.mt = mt;
 206             this.recipe = recipe;
 207         }
 208 
 209         @Override
 210         public boolean equals(Object o) {
 211             if (this == o) return true;
 212             if (o == null || getClass() != o.getClass()) return false;
 213 
 214             Key key = (Key) o;
 215 
 216             if (!mt.equals(key.mt)) return false;
 217             if (!recipe.equals(key.recipe)) return false;
 218             return true;
 219         }
 220 
 221         @Override
 222         public int hashCode() {
 223             int result = mt.hashCode();
 224             result = 31 * result + recipe.hashCode();
 225             return result;
 226         }
 227     }
 228 
 229     /**
 230      * Parses the recipe string, and produces the traversable collection of
 231      * {@link java.lang.invoke.StringConcatFactory.RecipeElement}-s for generator
 232      * strategies. Notably, this class parses out the constants from the recipe
 233      * and from other static arguments.
 234      */
 235     private static final class Recipe {
 236         private final List<RecipeElement> elements;
 237         private final List<RecipeElement> elementsRev;
 238 
 239         public Recipe(String src, Object[] constants) {
 240             List<RecipeElement> el = new ArrayList<>();
 241 
 242             int constC = 0;
 243             int argC = 0;
 244 
 245             StringBuilder acc = new StringBuilder();
 246 
 247             for (int i = 0; i < src.length(); i++) {
 248                 char c = src.charAt(i);
 249 
 250                 if (c == TAG_CONST || c == TAG_ARG) {
 251                     // Detected a special tag, flush all accumulated characters
 252                     // as a constant first:
 253                     if (acc.length() > 0) {
 254                         el.add(new RecipeElement(acc.toString()));
 255                         acc.setLength(0);
 256                     }
 257                     if (c == TAG_CONST) {
 258                         Object cnst = constants[constC++];
 259                         el.add(new RecipeElement(cnst));
 260                     }
 261                     if (c == TAG_ARG) {
 262                         el.add(new RecipeElement(argC++));
 263                     }
 264                 } else {
 265                     // Not a special characters, this is a constant embedded into
 266                     // the recipe itself.
 267                     acc.append(c);
 268                 }
 269             }
 270 
 271             // Flush the remaining characters as constant:
 272             if (acc.length() > 0) {
 273                 el.add(new RecipeElement(acc.toString()));
 274             }
 275 
 276             elements = new ArrayList<>(el);
 277             Collections.reverse(el);
 278             elementsRev = el;
 279         }
 280 
 281         public Collection<RecipeElement> getElements() {
 282             return elements;
 283         }
 284 
 285         public Collection<RecipeElement> getElementsReversed() {
 286             return elementsRev;
 287         }
 288 
 289         @Override
 290         public boolean equals(Object o) {
 291             if (this == o) return true;
 292             if (o == null || getClass() != o.getClass()) return false;
 293 
 294             Recipe recipe = (Recipe) o;
 295             return elements.equals(recipe.elements);
 296         }
 297 
 298         @Override
 299         public int hashCode() {
 300             return elements.hashCode();
 301         }
 302     }
 303 
 304     private static final class RecipeElement {
 305         private final Object value;
 306         private final int argPos;
 307         private final Tag tag;
 308 
 309         public RecipeElement(Object cnst) {
 310             this.value = Objects.requireNonNull(cnst);
 311             this.argPos = -1;
 312             this.tag = Tag.CONST;
 313         }
 314 
 315         public RecipeElement(int arg) {
 316             this.value = null;
 317             this.argPos = arg;
 318             this.tag = Tag.ARG;
 319         }
 320 
 321         public Object getValue() {
 322             assert (tag == Tag.CONST);
 323             return value;
 324         }
 325 
 326         public int getArgPos() {
 327             assert (tag == Tag.ARG);
 328             return argPos;
 329         }
 330 
 331         public Tag getTag() {
 332             return tag;
 333         }
 334 
 335         @Override
 336         public boolean equals(Object o) {
 337             if (this == o) return true;
 338             if (o == null || getClass() != o.getClass()) return false;
 339 
 340             RecipeElement that = (RecipeElement) o;
 341 
 342             if (tag != that.tag) return false;
 343             if (tag == Tag.CONST && (!value.equals(that.value))) return false;
 344             if (tag == Tag.ARG && (argPos != that.argPos)) return false;
 345             return true;
 346         }
 347 
 348         @Override
 349         public int hashCode() {
 350             return tag.hashCode();
 351         }
 352     }
 353 
 354     private enum Tag {
 355         CONST, ARG
 356     }
 357 
 358     /**
 359      * Facilitates the creation of optimized String concatenation methods, that
 360      * can be used to efficiently concatenate a known number of arguments of
 361      * known types, possibly after type adaptation and partial evaluation of
 362      * arguments. Typically used as a <em>bootstrap method</em> for {@code
 363      * invokedynamic} call sites, to support the <em>string concatenation</em>
 364      * feature of the Java Programming Language.
 365      *
 366      * <p>When the target of the {@code CallSite} returned from this method is
 367      * invoked, it returns the result of String concatenation, taking all
 368      * function arguments passed to the linkage method as inputs for
 369      * concatenation. The target signature is given by {@code concatType}.
 370      * The arguments are concatenated as per requirements stated in JLS 15.18.1
 371      * "String Concatenation Operator +". Notably, the inputs are converted as
 372      * per JLS 5.1.11 "String Conversion", and combined from left to right.
 373      *
 374      * <p>Assume the linkage arguments are as follows:
 375      *
 376      * <ul>
 377      *     <li>{@code concatType}, describing the {@code CallSite} signature</li>
 378      * </ul>
 379      *
 380      * <p>Then the following linkage invariants must hold:
 381      *
 382      * <ul>
 383      *     <li>The parameter count in {@code concatType} is less than or equal to 200</li>
 384      *
 385      *     <li>The return type in {@code concatType} is assignable from {@link java.lang.String}</li>
 386      * </ul>
 387      *
 388      * @param lookup   Represents a lookup context with the accessibility
 389      *                 privileges of the caller.  When used with {@code
 390      *                 invokedynamic}, this is stacked automatically by the VM.
 391      * @param name     The name of the method to implement. This name is
 392      *                 arbitrary, and has no meaning for this linkage method.
 393      *                 When used with {@code invokedynamic}, this is provided by
 394      *                 the {@code NameAndType} of the {@code InvokeDynamic}
 395      *                 structure and is stacked automatically by the VM.
 396      * @param concatType The expected signature of the {@code CallSite}.  The
 397      *                   parameter types represent the types of concatenation
 398      *                   arguments; the return type is always assignable from {@link
 399      *                   java.lang.String}.  When used with {@code invokedynamic},
 400      *                   this is provided by the {@code NameAndType} of the {@code
 401      *                   InvokeDynamic} structure and is stacked automatically by
 402      *                   the VM.
 403      * @return a CallSite whose target can be used to perform String
 404      * concatenation, with dynamic concatenation arguments described by the given
 405      * {@code concatType}.
 406      * @throws StringConcatException If any of the linkage invariants described
 407      *                               here are violated.
 408      * @throws NullPointerException If any of the incoming arguments is null.
 409      *                              This will never happen when a bootstrap method
 410      *                              is called with invokedynamic.
 411      *
 412      * @jls  5.1.11 String Conversion
 413      * @jls 15.18.1 String Concatenation Operator +
 414      */
 415     public static CallSite makeConcat(MethodHandles.Lookup lookup,
 416                                       String name,
 417                                       MethodType concatType) throws StringConcatException {
 418         if (DEBUG) {
 419             System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType);
 420         }
 421 
 422         return doStringConcat(lookup, name, concatType, true, null);
 423     }
 424 
 425     /**
 426      * Facilitates the creation of optimized String concatenation methods, that
 427      * can be used to efficiently concatenate a known number of arguments of
 428      * known types, possibly after type adaptation and partial evaluation of
 429      * arguments. Typically used as a <em>bootstrap method</em> for {@code
 430      * invokedynamic} call sites, to support the <em>string concatenation</em>
 431      * feature of the Java Programming Language.
 432      *
 433      * <p>When the target of the {@code CallSite} returned from this method is
 434      * invoked, it returns the result of String concatenation, taking all
 435      * function arguments and constants passed to the linkage method as inputs for
 436      * concatenation. The target signature is given by {@code concatType}, and
 437      * does not include constants. The arguments are concatenated as per requirements
 438      * stated in JLS 15.18.1 "String Concatenation Operator +". Notably, the inputs
 439      * are converted as per JLS 5.1.11 "String Conversion", and combined from left
 440      * to right.
 441      *
 442      * <p>The concatenation <em>recipe</em> is a String description for the way to
 443      * construct a concatenated String from the arguments and constants. The
 444      * recipe is processed from left to right, and each character represents an
 445      * input to concatenation. Recipe characters mean:
 446      *
 447      * <ul>
 448      *
 449      *   <li><em>\1 (Unicode point 0001)</em>: an ordinary argument. This
 450      *   input is passed through dynamic argument, and is provided during the
 451      *   concatenation method invocation. This input can be null.</li>
 452      *
 453      *   <li><em>\2 (Unicode point 0002):</em> a constant. This input passed
 454      *   through static bootstrap argument. This constant can be any value
 455      *   representable in constant pool. If necessary, the factory would call
 456      *   {@code toString} to perform a one-time String conversion.</li>
 457      *
 458      *   <li><em>Any other char value:</em> a single character constant.</li>
 459      * </ul>
 460      *
 461      * <p>Assume the linkage arguments are as follows:
 462      *
 463      * <ul>
 464      *   <li>{@code concatType}, describing the {@code CallSite} signature</li>
 465      *   <li>{@code recipe}, describing the String recipe</li>
 466      *   <li>{@code constants}, the vararg array of constants</li>
 467      * </ul>
 468      *
 469      * <p>Then the following linkage invariants must hold:
 470      *
 471      * <ul>
 472      *   <li>The parameter count in {@code concatType} is less than or equal to
 473      *   200</li>
 474      *
 475      *   <li>The parameter count in {@code concatType} equals to number of \1 tags
 476      *   in {@code recipe}</li>
 477      *
 478      *   <li>The return type in {@code concatType} is assignable
 479      *   from {@link java.lang.String}, and matches the return type of the
 480      *   returned {@link MethodHandle}</li>
 481      *
 482      *   <li>The number of elements in {@code constants} equals to number of \2
 483      *   tags in {@code recipe}</li>
 484      * </ul>
 485      *
 486      * @param lookup    Represents a lookup context with the accessibility
 487      *                  privileges of the caller. When used with {@code
 488      *                  invokedynamic}, this is stacked automatically by the
 489      *                  VM.
 490      * @param name      The name of the method to implement. This name is
 491      *                  arbitrary, and has no meaning for this linkage method.
 492      *                  When used with {@code invokedynamic}, this is provided
 493      *                  by the {@code NameAndType} of the {@code InvokeDynamic}
 494      *                  structure and is stacked automatically by the VM.
 495      * @param concatType The expected signature of the {@code CallSite}.  The
 496      *                  parameter types represent the types of dynamic concatenation
 497      *                  arguments; the return type is always assignable from {@link
 498      *                  java.lang.String}.  When used with {@code
 499      *                  invokedynamic}, this is provided by the {@code
 500      *                  NameAndType} of the {@code InvokeDynamic} structure and
 501      *                  is stacked automatically by the VM.
 502      * @param recipe    Concatenation recipe, described above.
 503      * @param constants A vararg parameter representing the constants passed to
 504      *                  the linkage method.
 505      * @return a CallSite whose target can be used to perform String
 506      * concatenation, with dynamic concatenation arguments described by the given
 507      * {@code concatType}.
 508      * @throws StringConcatException If any of the linkage invariants described
 509      *                               here are violated.
 510      * @throws NullPointerException If any of the incoming arguments is null, or
 511      *                              any constant in {@code recipe} is null.
 512      *                              This will never happen when a bootstrap method
 513      *                              is called with invokedynamic.
 514      * @apiNote Code generators have three distinct ways to process a constant
 515      * string operand S in a string concatenation expression.  First, S can be
 516      * materialized as a reference (using ldc) and passed as an ordinary argument
 517      * (recipe '\1'). Or, S can be stored in the constant pool and passed as a
 518      * constant (recipe '\2') . Finally, if S contains neither of the recipe
 519      * tag characters ('\1', '\2') then S can be interpolated into the recipe
 520      * itself, causing its characters to be inserted into the result.
 521      *
 522      * @jls  5.1.11 String Conversion
 523      * @jls 15.18.1 String Concatenation Operator +
 524      */
 525     public static CallSite makeConcatWithConstants(MethodHandles.Lookup lookup,
 526                                                    String name,
 527                                                    MethodType concatType,
 528                                                    String recipe,
 529                                                    Object... constants) throws StringConcatException {
 530         if (DEBUG) {
 531             System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType + ", {" + recipe + "}, " + Arrays.toString(constants));
 532         }
 533 
 534         return doStringConcat(lookup, name, concatType, false, recipe, constants);
 535     }
 536 
 537     private static CallSite doStringConcat(MethodHandles.Lookup lookup,
 538                                            String name,
 539                                            MethodType concatType,
 540                                            boolean generateRecipe,
 541                                            String recipe,
 542                                            Object... constants) throws StringConcatException {
 543         Objects.requireNonNull(lookup, "Lookup is null");
 544         Objects.requireNonNull(name, "Name is null");
 545         Objects.requireNonNull(concatType, "Concat type is null");
 546         Objects.requireNonNull(constants, "Constants are null");
 547 
 548         for (Object o : constants) {
 549             Objects.requireNonNull(o, "Cannot accept null constants");
 550         }
 551 
 552         int cCount = 0;
 553         int oCount = 0;
 554         if (generateRecipe) {
 555             // Mock the recipe to reuse the concat generator code
 556             char[] value = new char[concatType.parameterCount()];
 557             Arrays.fill(value, TAG_ARG);
 558             recipe = new String(value);
 559             oCount = concatType.parameterCount();
 560         } else {
 561             Objects.requireNonNull(recipe, "Recipe is null");
 562 
 563             for (int i = 0; i < recipe.length(); i++) {
 564                 char c = recipe.charAt(i);
 565                 if (c == TAG_CONST) cCount++;
 566                 if (c == TAG_ARG)   oCount++;
 567             }
 568         }
 569 
 570         if (oCount != concatType.parameterCount()) {
 571             throw new StringConcatException(
 572                     "Mismatched number of concat arguments: recipe wants " +
 573                             oCount +
 574                             " arguments, but signature provides " +
 575                             concatType.parameterCount());
 576         }
 577 
 578         if (cCount != constants.length) {
 579             throw new StringConcatException(
 580                     "Mismatched number of concat constants: recipe wants " +
 581                             cCount +
 582                             " constants, but only " +
 583                             constants.length +
 584                             " are passed");
 585         }
 586 
 587         if (!concatType.returnType().isAssignableFrom(String.class)) {
 588             throw new StringConcatException(
 589                     "The return type should be compatible with String, but it is " +
 590                             concatType.returnType());
 591         }
 592 
 593         if (concatType.parameterCount() > MAX_INDY_CONCAT_ARG_SLOTS) {
 594             throw new StringConcatException("Too many concat argument slots: " +
 595                     concatType.parameterCount() +
 596                     ", can only accept " +
 597                     MAX_INDY_CONCAT_ARG_SLOTS);
 598         }
 599 
 600         MethodType mt = adaptType(concatType);
 601 
 602         Recipe rec = new Recipe(recipe, constants);
 603 
 604         MethodHandle mh;
 605         if (CACHE_ENABLE) {
 606             Key key = new Key(mt, rec);
 607             mh = CACHE.get(key);
 608             if (mh == null) {
 609                 mh = generate(lookup, mt, rec);
 610                 CACHE.put(key, mh);
 611             }
 612         } else {
 613             mh = generate(lookup, mt, rec);
 614         }
 615         return new ConstantCallSite(mh.asType(concatType));
 616     }
 617 
 618     /**
 619      * Adapt method type to an API we are going to use.
 620      *
 621      * This strips the concrete classes from the signatures, thus preventing
 622      * class leakage when we cache the concatenation stubs.
 623      *
 624      * @param args actual argument types
 625      * @return argument types the strategy is going to use
 626      */
 627     private static MethodType adaptType(MethodType args) {
 628         Class<?>[] ptypes = args.parameterArray();
 629         boolean changed = false;
 630         for (int i = 0; i < ptypes.length; i++) {
 631             Class<?> ptype = ptypes[i];
 632             if (!ptype.isPrimitive() &&
 633                     ptype != String.class &&
 634                     ptype != Object.class) { // truncate to Object
 635                 ptypes[i] = Object.class;
 636                 changed = true;
 637             }
 638             // else other primitives or String or Object (unchanged)
 639         }
 640         return changed
 641                 ? MethodType.methodType(args.returnType(), ptypes)
 642                 : args;
 643     }
 644 
 645     private static MethodHandle generate(Lookup lookup, MethodType mt, Recipe recipe) throws StringConcatException {
 646         try {
 647             switch (STRATEGY) {
 648                 case BC_SB:
 649                     return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.DEFAULT);
 650                 case BC_SB_SIZED:
 651                     return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.SIZED);
 652                 case BC_SB_SIZED_EXACT:
 653                     return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.SIZED_EXACT);
 654                 case MH_SB_SIZED:
 655                     return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED);
 656                 case MH_SB_SIZED_EXACT:
 657                     return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT);
 658                 case MH_INLINE_SIZED_EXACT:
 659                     return MethodHandleInlineCopyStrategy.generate(mt, recipe);
 660                 default:
 661                     throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
 662             }
 663         } catch (Throwable t) {
 664             throw new StringConcatException("Generator failed", t);
 665         }
 666     }
 667 
 668     private enum Mode {
 669         DEFAULT(false, false),
 670         SIZED(true, false),
 671         SIZED_EXACT(true, true);
 672 
 673         private final boolean sized;
 674         private final boolean exact;
 675 
 676         Mode(boolean sized, boolean exact) {
 677             this.sized = sized;
 678             this.exact = exact;
 679         }
 680 
 681         boolean isSized() {
 682             return sized;
 683         }
 684 
 685         boolean isExact() {
 686             return exact;
 687         }
 688     }
 689 
 690     /**
 691      * Bytecode StringBuilder strategy.
 692      *
 693      * <p>This strategy operates in three modes, gated by {@link Mode}.
 694      *
 695      * <p><b>{@link Strategy#BC_SB}: "bytecode StringBuilder".</b>
 696      *
 697      * <p>This strategy spins up the bytecode that has the same StringBuilder
 698      * chain javac would otherwise emit. This strategy uses only the public API,
 699      * and comes as the baseline for the current JDK behavior. On other words,
 700      * this strategy moves the javac generated bytecode to runtime. The
 701      * generated bytecode is loaded via Unsafe.defineAnonymousClass, but with
 702      * the caller class coming from the BSM -- in other words, the protection
 703      * guarantees are inherited from the method where invokedynamic was
 704      * originally called. This means, among other things, that the bytecode is
 705      * verified for all non-JDK uses.
 706      *
 707      * <p><b>{@link Strategy#BC_SB_SIZED}: "bytecode StringBuilder, but
 708      * sized".</b>
 709      *
 710      * <p>This strategy acts similarly to {@link Strategy#BC_SB}, but it also
 711      * tries to guess the capacity required for StringBuilder to accept all
 712      * arguments without resizing. This strategy only makes an educated guess:
 713      * it only guesses the space required for known types (e.g. primitives and
 714      * Strings), but does not otherwise convert arguments. Therefore, the
 715      * capacity estimate may be wrong, and StringBuilder may have to
 716      * transparently resize or trim when doing the actual concatenation. While
 717      * this does not constitute a correctness issue (in the end, that what BC_SB
 718      * has to do anyway), this does pose a potential performance problem.
 719      *
 720      * <p><b>{@link Strategy#BC_SB_SIZED_EXACT}: "bytecode StringBuilder, but
 721      * sized exactly".</b>
 722      *
 723      * <p>This strategy improves on @link Strategy#BC_SB_SIZED}, by first
 724      * converting all arguments to String in order to get the exact capacity
 725      * StringBuilder should have. The conversion is done via the public
 726      * String.valueOf and/or Object.toString methods, and does not touch any
 727      * private String API.
 728      */
 729     private static final class BytecodeStringBuilderStrategy {
 730         static final Unsafe UNSAFE = Unsafe.getUnsafe();
 731         static final int CLASSFILE_VERSION = 52;
 732         static final String NAME_FACTORY = "concat";
 733         static final String CLASS_NAME = "java/lang/String$Concat";
 734 
 735         private BytecodeStringBuilderStrategy() {
 736             // no instantiation
 737         }
 738 
 739         private static MethodHandle generate(MethodHandles.Lookup lookup, MethodType args, Recipe recipe, Mode mode) throws Exception {
 740             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
 741 
 742             cw.visit(CLASSFILE_VERSION,
 743                     ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
 744                     CLASS_NAME,
 745                     null,
 746                     "java/lang/Object",
 747                     null
 748             );
 749 
 750             MethodVisitor mv = cw.visitMethod(
 751                     ACC_PUBLIC + ACC_STATIC + ACC_FINAL,
 752                     NAME_FACTORY,
 753                     args.toMethodDescriptorString(),
 754                     null,
 755                     null);
 756 
 757             mv.visitAnnotation("Ljava/lang/invoke/ForceInline;", true);
 758             mv.visitCode();
 759 
 760             Class<?>[] arr = args.parameterArray();
 761             boolean[] guaranteedNonNull = new boolean[arr.length];
 762 
 763             if (mode.isExact()) {
 764                 /*
 765                     In exact mode, we need to convert all arguments to their String representations,
 766                     as this allows to compute their String sizes exactly. We cannot use private
 767                     methods for primitives in here, therefore we need to convert those as well.
 768 
 769                     We also record what arguments are guaranteed to be non-null as the result
 770                     of the conversion. String.valueOf does the null checks for us. The only
 771                     corner case to take care of is String.valueOf(Object) returning null itself.
 772 
 773                     Also, if any conversion happened, then the slot indices in the incoming
 774                     arguments are not equal to the final local maps. The only case this may break
 775                     is when converting 2-slot long/double argument to 1-slot String. Therefore,
 776                     we get away with tracking modified offset, since no conversion can overwrite
 777                     the upcoming the argument.
 778                  */
 779 
 780                 int off = 0;
 781                 int modOff = 0;
 782                 for (int c = 0; c < arr.length; c++) {
 783                     Class<?> cl = arr[c];
 784                     if (cl == String.class) {
 785                         if (off != modOff) {
 786                             mv.visitIntInsn(getLoadOpcode(cl), off);
 787                             mv.visitIntInsn(ASTORE, modOff);
 788                         }
 789                     } else {
 790                         mv.visitIntInsn(getLoadOpcode(cl), off);
 791                         mv.visitMethodInsn(
 792                                 INVOKESTATIC,
 793                                 "java/lang/String",
 794                                 "valueOf",
 795                                 getStringValueOfDesc(cl),
 796                                 false
 797                         );
 798                         mv.visitIntInsn(ASTORE, modOff);
 799                         arr[c] = String.class;
 800                         guaranteedNonNull[c] = cl.isPrimitive();
 801                     }
 802                     off += getParameterSize(cl);
 803                     modOff += getParameterSize(String.class);
 804                 }
 805             }
 806 
 807             if (mode.isSized()) {
 808                 /*
 809                     When operating in sized mode (this includes exact mode), it makes sense to make
 810                     StringBuilder append chains look familiar to OptimizeStringConcat. For that, we
 811                     need to do null-checks early, not make the append chain shape simpler.
 812                  */
 813 
 814                 int off = 0;
 815                 for (RecipeElement el : recipe.getElements()) {
 816                     switch (el.getTag()) {
 817                         case CONST: {
 818                             // Guaranteed non-null, no null check required.
 819                             break;
 820                         }
 821                         case ARG: {
 822                             // Null-checks are needed only for String arguments, and when a previous stage
 823                             // did not do implicit null-checks. If a String is null, we eagerly replace it
 824                             // with "null" constant. Note, we omit Objects here, because we don't call
 825                             // .length() on them down below.
 826                             int ac = el.getArgPos();
 827                             Class<?> cl = arr[ac];
 828                             if (cl == String.class && !guaranteedNonNull[ac]) {
 829                                 Label l0 = new Label();
 830                                 mv.visitIntInsn(ALOAD, off);
 831                                 mv.visitJumpInsn(IFNONNULL, l0);
 832                                 mv.visitLdcInsn("null");
 833                                 mv.visitIntInsn(ASTORE, off);
 834                                 mv.visitLabel(l0);
 835                             }
 836                             off += getParameterSize(cl);
 837                             break;
 838                         }
 839                         default:
 840                             throw new StringConcatException("Unhandled tag: " + el.getTag());
 841                     }
 842                 }
 843             }
 844 
 845             // Prepare StringBuilder instance
 846             mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
 847             mv.visitInsn(DUP);
 848 
 849             if (mode.isSized()) {
 850                 /*
 851                     Sized mode requires us to walk through the arguments, and estimate the final length.
 852                     In exact mode, this will operate on Strings only. This code would accumulate the
 853                     final length on stack.
 854                  */
 855                 int len = 0;
 856                 int off = 0;
 857 
 858                 mv.visitInsn(ICONST_0);
 859 
 860                 for (RecipeElement el : recipe.getElements()) {
 861                     switch (el.getTag()) {
 862                         case CONST: {
 863                             Object cnst = el.getValue();
 864                             len += cnst.toString().length();
 865                             break;
 866                         }
 867                         case ARG: {
 868                             /*
 869                                 If an argument is String, then we can call .length() on it. Sized/Exact modes have
 870                                 converted arguments for us. If an argument is primitive, we can provide a guess
 871                                 for its String representation size.
 872                             */
 873                             Class<?> cl = arr[el.getArgPos()];
 874                             if (cl == String.class) {
 875                                 mv.visitIntInsn(ALOAD, off);
 876                                 mv.visitMethodInsn(
 877                                         INVOKEVIRTUAL,
 878                                         "java/lang/String",
 879                                         "length",
 880                                         "()I",
 881                                         false
 882                                 );
 883                                 mv.visitInsn(IADD);
 884                             } else if (cl.isPrimitive()) {
 885                                 len += estimateSize(cl);
 886                             }
 887                             off += getParameterSize(cl);
 888                             break;
 889                         }
 890                         default:
 891                             throw new StringConcatException("Unhandled tag: " + el.getTag());
 892                     }
 893                 }
 894 
 895                 // Constants have non-zero length, mix in
 896                 if (len > 0) {
 897                     iconst(mv, len);
 898                     mv.visitInsn(IADD);
 899                 }
 900 
 901                 mv.visitMethodInsn(
 902                         INVOKESPECIAL,
 903                         "java/lang/StringBuilder",
 904                         "<init>",
 905                         "(I)V",
 906                         false
 907                 );
 908             } else {
 909                 mv.visitMethodInsn(
 910                         INVOKESPECIAL,
 911                         "java/lang/StringBuilder",
 912                         "<init>",
 913                         "()V",
 914                         false
 915                 );
 916             }
 917 
 918             // At this point, we have a blank StringBuilder on stack, fill it in with .append calls.
 919             {
 920                 int off = 0;
 921                 for (RecipeElement el : recipe.getElements()) {
 922                     String desc;
 923                     switch (el.getTag()) {
 924                         case CONST: {
 925                             Object cnst = el.getValue();
 926                             mv.visitLdcInsn(cnst);
 927                             desc = getSBAppendDesc(cnst.getClass());
 928                             break;
 929                         }
 930                         case ARG: {
 931                             Class<?> cl = arr[el.getArgPos()];
 932                             mv.visitVarInsn(getLoadOpcode(cl), off);
 933                             off += getParameterSize(cl);
 934                             desc = getSBAppendDesc(cl);
 935                             break;
 936                         }
 937                         default:
 938                             throw new StringConcatException("Unhandled tag: " + el.getTag());
 939                     }
 940                     mv.visitMethodInsn(
 941                             INVOKEVIRTUAL,
 942                             "java/lang/StringBuilder",
 943                             "append",
 944                             desc,
 945                             false
 946                     );
 947                 }
 948             }
 949 
 950             if (DEBUG && mode.isExact()) {
 951                 /*
 952                     Exactness checks compare the final StringBuilder.capacity() with a resulting
 953                     String.length(). If these values disagree, that means StringBuilder had to perform
 954                     storage trimming, which defeats the purpose of exact strategies.
 955                  */
 956 
 957                 mv.visitInsn(DUP);
 958 
 959                 mv.visitMethodInsn(
 960                         INVOKEVIRTUAL,
 961                         "java/lang/StringBuilder",
 962                         "capacity",
 963                         "()I",
 964                         false
 965                 );
 966 
 967                 mv.visitIntInsn(ISTORE, 0);
 968 
 969                 mv.visitMethodInsn(
 970                         INVOKEVIRTUAL,
 971                         "java/lang/StringBuilder",
 972                         "toString",
 973                         "()Ljava/lang/String;",
 974                         false
 975                 );
 976 
 977                 mv.visitInsn(DUP);
 978 
 979                 mv.visitMethodInsn(
 980                         INVOKEVIRTUAL,
 981                         "java/lang/String",
 982                         "length",
 983                         "()I",
 984                         false
 985                 );
 986 
 987                 mv.visitIntInsn(ILOAD, 0);
 988 
 989                 Label l0 = new Label();
 990                 mv.visitJumpInsn(IF_ICMPEQ, l0);
 991 
 992                 mv.visitTypeInsn(NEW, "java/lang/AssertionError");
 993                 mv.visitInsn(DUP);
 994                 mv.visitLdcInsn("Failed exactness check");
 995                 mv.visitMethodInsn(INVOKESPECIAL,
 996                         "java/lang/AssertionError",
 997                         "<init>",
 998                         "(Ljava/lang/Object;)V",
 999                         false);
1000                 mv.visitInsn(ATHROW);
1001 
1002                 mv.visitLabel(l0);
1003             } else {
1004                 mv.visitMethodInsn(
1005                         INVOKEVIRTUAL,
1006                         "java/lang/StringBuilder",
1007                         "toString",
1008                         "()Ljava/lang/String;",
1009                         false
1010                 );
1011             }
1012 
1013             mv.visitInsn(ARETURN);
1014 
1015             mv.visitMaxs(-1, -1);
1016             mv.visitEnd();
1017             cw.visitEnd();
1018 
1019             Class<?> targetClass = lookup.lookupClass();
1020             final byte[] classBytes = cw.toByteArray();
1021             final Class<?> innerClass = UNSAFE.defineAnonymousClass(targetClass, classBytes, null);
1022 
1023             try {
1024                 UNSAFE.ensureClassInitialized(innerClass);
1025                 return lookup.findStatic(innerClass, NAME_FACTORY, args);
1026             } catch (ReflectiveOperationException e) {
1027                 throw new StringConcatException("Exception finding constructor", e);
1028             }
1029         }
1030 
1031         private static String getSBAppendDesc(Class<?> cl) {
1032             if (cl.isPrimitive()) {
1033                 if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) {
1034                     return "(I)Ljava/lang/StringBuilder;";
1035                 } else if (cl == Boolean.TYPE) {
1036                     return "(Z)Ljava/lang/StringBuilder;";
1037                 } else if (cl == Character.TYPE) {
1038                     return "(C)Ljava/lang/StringBuilder;";
1039                 } else if (cl == Double.TYPE) {
1040                     return "(D)Ljava/lang/StringBuilder;";
1041                 } else if (cl == Float.TYPE) {
1042                     return "(F)Ljava/lang/StringBuilder;";
1043                 } else if (cl == Long.TYPE) {
1044                     return "(J)Ljava/lang/StringBuilder;";
1045                 } else {
1046                     throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl);
1047                 }
1048             } else if (cl == String.class) {
1049                 return "(Ljava/lang/String;)Ljava/lang/StringBuilder;";
1050             } else {
1051                 return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;";
1052             }
1053         }
1054 
1055         private static String getStringValueOfDesc(Class<?> cl) {
1056             if (cl.isPrimitive()) {
1057                 if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) {
1058                     return "(I)Ljava/lang/String;";
1059                 } else if (cl == Boolean.TYPE) {
1060                     return "(Z)Ljava/lang/String;";
1061                 } else if (cl == Character.TYPE) {
1062                     return "(C)Ljava/lang/String;";
1063                 } else if (cl == Double.TYPE) {
1064                     return "(D)Ljava/lang/String;";
1065                 } else if (cl == Float.TYPE) {
1066                     return "(F)Ljava/lang/String;";
1067                 } else if (cl == Long.TYPE) {
1068                     return "(J)Ljava/lang/String;";
1069                 } else {
1070                     throw new IllegalStateException("Unhandled String.valueOf: " + cl);
1071                 }
1072             } else if (cl == String.class) {
1073                 return "(Ljava/lang/String;)Ljava/lang/String;";
1074             } else {
1075                 return "(Ljava/lang/Object;)Ljava/lang/String;";
1076             }
1077         }
1078 
1079         /**
1080          * The following method is copied from
1081          * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small
1082          * and fast Java bytecode manipulation framework.
1083          * Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved.
1084          */
1085         private static void iconst(MethodVisitor mv, final int cst) {
1086             if (cst >= -1 && cst <= 5) {
1087                 mv.visitInsn(Opcodes.ICONST_0 + cst);
1088             } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) {
1089                 mv.visitIntInsn(Opcodes.BIPUSH, cst);
1090             } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) {
1091                 mv.visitIntInsn(Opcodes.SIPUSH, cst);
1092             } else {
1093                 mv.visitLdcInsn(cst);
1094             }
1095         }
1096 
1097         private static int getLoadOpcode(Class<?> c) {
1098             if (c == Void.TYPE) {
1099                 throw new InternalError("Unexpected void type of load opcode");
1100             }
1101             return ILOAD + getOpcodeOffset(c);
1102         }
1103 
1104         private static int getOpcodeOffset(Class<?> c) {
1105             if (c.isPrimitive()) {
1106                 if (c == Long.TYPE) {
1107                     return 1;
1108                 } else if (c == Float.TYPE) {
1109                     return 2;
1110                 } else if (c == Double.TYPE) {
1111                     return 3;
1112                 }
1113                 return 0;
1114             } else {
1115                 return 4;
1116             }
1117         }
1118 
1119         private static int getParameterSize(Class<?> c) {
1120             if (c == Void.TYPE) {
1121                 return 0;
1122             } else if (c == Long.TYPE || c == Double.TYPE) {
1123                 return 2;
1124             }
1125             return 1;
1126         }
1127     }
1128 
1129     /**
1130      * MethodHandle StringBuilder strategy.
1131      *
1132      * <p>This strategy operates in two modes, gated by {@link Mode}.
1133      *
1134      * <p><b>{@link Strategy#MH_SB_SIZED}: "MethodHandles StringBuilder,
1135      * sized".</b>
1136      *
1137      * <p>This strategy avoids spinning up the bytecode by building the
1138      * computation on MethodHandle combinators. The computation is built with
1139      * public MethodHandle APIs, resolved from a public Lookup sequence, and
1140      * ends up calling the public StringBuilder API. Therefore, this strategy
1141      * does not use any private API at all, even the Unsafe.defineAnonymousClass,
1142      * since everything is handled under cover by java.lang.invoke APIs.
1143      *
1144      * <p><b>{@link Strategy#MH_SB_SIZED_EXACT}: "MethodHandles StringBuilder,
1145      * sized exactly".</b>
1146      *
1147      * <p>This strategy improves on @link Strategy#MH_SB_SIZED}, by first
1148      * converting all arguments to String in order to get the exact capacity
1149      * StringBuilder should have. The conversion is done via the public
1150      * String.valueOf and/or Object.toString methods, and does not touch any
1151      * private String API.
1152      */
1153     private static final class MethodHandleStringBuilderStrategy {
1154 
1155         private MethodHandleStringBuilderStrategy() {
1156             // no instantiation
1157         }
1158 
1159         private static MethodHandle generate(MethodType mt, Recipe recipe, Mode mode) throws Exception {
1160             int pc = mt.parameterCount();
1161 
1162             Class<?>[] ptypes = mt.parameterArray();
1163             MethodHandle[] filters = new MethodHandle[ptypes.length];
1164             for (int i = 0; i < ptypes.length; i++) {
1165                 MethodHandle filter;
1166                 switch (mode) {
1167                     case SIZED:
1168                         // In sized mode, we convert all references and floats/doubles
1169                         // to String: there is no specialization for different
1170                         // classes in StringBuilder API, and it will convert to
1171                         // String internally anyhow.
1172                         filter = Stringifiers.forMost(ptypes[i]);
1173                         break;
1174                     case SIZED_EXACT:
1175                         // In exact mode, we convert everything to String:
1176                         // this helps to compute the storage exactly.
1177                         filter = Stringifiers.forAny(ptypes[i]);
1178                         break;
1179                     default:
1180                         throw new StringConcatException("Not supported");
1181                 }
1182                 if (filter != null) {
1183                     filters[i] = filter;
1184                     ptypes[i] = filter.type().returnType();
1185                 }
1186             }
1187 
1188             List<Class<?>> ptypesList = Arrays.asList(ptypes);
1189             MethodHandle[] lengthers = new MethodHandle[pc];
1190 
1191             // Figure out lengths: constants' lengths can be deduced on the spot.
1192             // All reference arguments were filtered to String in the combinators below, so we can
1193             // call the usual String.length(). Primitive values string sizes can be estimated.
1194             int initial = 0;
1195             for (RecipeElement el : recipe.getElements()) {
1196                 switch (el.getTag()) {
1197                     case CONST: {
1198                         Object cnst = el.getValue();
1199                         initial += cnst.toString().length();
1200                         break;
1201                     }
1202                     case ARG: {
1203                         final int i = el.getArgPos();
1204                         Class<?> type = ptypesList.get(i);
1205                         if (type.isPrimitive()) {
1206                             MethodHandle est = MethodHandles.constant(int.class, estimateSize(type));
1207                             est = MethodHandles.dropArguments(est, 0, type);
1208                             lengthers[i] = est;
1209                         } else {
1210                             lengthers[i] = STRING_LENGTH;
1211                         }
1212                         break;
1213                     }
1214                     default:
1215                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1216                 }
1217             }
1218 
1219             // Create (StringBuilder, <args>) shape for appending:
1220             MethodHandle builder = MethodHandles.dropArguments(MethodHandles.identity(StringBuilder.class), 1, ptypesList);
1221 
1222             // Compose append calls. This is done in reverse because the application order is
1223             // reverse as well.
1224             for (RecipeElement el : recipe.getElementsReversed()) {
1225                 MethodHandle appender;
1226                 switch (el.getTag()) {
1227                     case CONST: {
1228                         Object constant = el.getValue();
1229                         MethodHandle mh = appender(adaptToStringBuilder(constant.getClass()));
1230                         appender = MethodHandles.insertArguments(mh, 1, constant);
1231                         break;
1232                     }
1233                     case ARG: {
1234                         int ac = el.getArgPos();
1235                         appender = appender(ptypesList.get(ac));
1236 
1237                         // Insert dummy arguments to match the prefix in the signature.
1238                         // The actual appender argument will be the ac-ith argument.
1239                         if (ac != 0) {
1240                             appender = MethodHandles.dropArguments(appender, 1, ptypesList.subList(0, ac));
1241                         }
1242                         break;
1243                     }
1244                     default:
1245                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1246                 }
1247                 builder = MethodHandles.foldArguments(builder, appender);
1248             }
1249 
1250             // Build the sub-tree that adds the sizes and produces a StringBuilder:
1251 
1252             // a) Start with the reducer that accepts all arguments, plus one
1253             //    slot for the initial value. Inject the initial value right away.
1254             //    This produces (<ints>)int shape:
1255             MethodHandle sum = getReducerFor(pc + 1);
1256             MethodHandle adder = MethodHandles.insertArguments(sum, 0, initial);
1257 
1258             // b) Apply lengthers to transform arguments to lengths, producing (<args>)int
1259             adder = MethodHandles.filterArguments(adder, 0, lengthers);
1260 
1261             // c) Instantiate StringBuilder (<args>)int -> (<args>)StringBuilder
1262             MethodHandle newBuilder = MethodHandles.filterReturnValue(adder, NEW_STRING_BUILDER);
1263 
1264             // d) Fold in StringBuilder constructor, this produces (<args>)StringBuilder
1265             MethodHandle mh = MethodHandles.foldArguments(builder, newBuilder);
1266 
1267             // Convert non-primitive arguments to Strings
1268             mh = MethodHandles.filterArguments(mh, 0, filters);
1269 
1270             // Convert (<args>)StringBuilder to (<args>)String
1271             if (DEBUG && mode.isExact()) {
1272                 mh = MethodHandles.filterReturnValue(mh, BUILDER_TO_STRING_CHECKED);
1273             } else {
1274                 mh = MethodHandles.filterReturnValue(mh, BUILDER_TO_STRING);
1275             }
1276 
1277             return mh;
1278         }
1279 
1280         private static MethodHandle getReducerFor(int cnt) {
1281             return SUMMERS.computeIfAbsent(cnt, SUMMER);
1282         }
1283 
1284         private static MethodHandle appender(Class<?> appendType) {
1285             MethodHandle appender = lookupVirtual(MethodHandles.publicLookup(), StringBuilder.class, "append",
1286                     StringBuilder.class, adaptToStringBuilder(appendType));
1287 
1288             // appenders should return void, this would not modify the target signature during folding
1289             MethodType nt = MethodType.methodType(void.class, StringBuilder.class, appendType);
1290             return appender.asType(nt);
1291         }
1292 
1293         private static String toStringChecked(StringBuilder sb) {
1294             String s = sb.toString();
1295             if (s.length() != sb.capacity()) {
1296                 throw new AssertionError("Exactness check failed: result length = " + s.length() + ", buffer capacity = " + sb.capacity());
1297             }
1298             return s;
1299         }
1300 
1301         private static int sum(int v1, int v2) {
1302             return v1 + v2;
1303         }
1304 
1305         private static int sum(int v1, int v2, int v3) {
1306             return v1 + v2 + v3;
1307         }
1308 
1309         private static int sum(int v1, int v2, int v3, int v4) {
1310             return v1 + v2 + v3 + v4;
1311         }
1312 
1313         private static int sum(int v1, int v2, int v3, int v4, int v5) {
1314             return v1 + v2 + v3 + v4 + v5;
1315         }
1316 
1317         private static int sum(int v1, int v2, int v3, int v4, int v5, int v6) {
1318             return v1 + v2 + v3 + v4 + v5 + v6;
1319         }
1320 
1321         private static int sum(int v1, int v2, int v3, int v4, int v5, int v6, int v7) {
1322             return v1 + v2 + v3 + v4 + v5 + v6 + v7;
1323         }
1324 
1325         private static int sum(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8) {
1326             return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8;
1327         }
1328 
1329         private static int sum(int initial, int[] vs) {
1330             int sum = initial;
1331             for (int v : vs) {
1332                 sum += v;
1333             }
1334             return sum;
1335         }
1336 
1337         private static final ConcurrentMap<Integer, MethodHandle> SUMMERS;
1338 
1339         // This one is deliberately non-lambdified to optimize startup time:
1340         private static final Function<Integer, MethodHandle> SUMMER = new Function<Integer, MethodHandle>() {
1341             @Override
1342             public MethodHandle apply(Integer cnt) {
1343                 if (cnt == 1) {
1344                     return MethodHandles.identity(int.class);
1345                 } else if (cnt <= 8) {
1346                     // Variable-arity collectors are not as efficient as small-count methods,
1347                     // unroll some initial sizes.
1348                     Class<?>[] cls = new Class<?>[cnt];
1349                     Arrays.fill(cls, int.class);
1350                     return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls);
1351                 } else {
1352                     return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class)
1353                             .asCollector(int[].class, cnt - 1);
1354                 }
1355             }
1356         };
1357 
1358         private static final MethodHandle NEW_STRING_BUILDER, STRING_LENGTH, BUILDER_TO_STRING, BUILDER_TO_STRING_CHECKED;
1359 
1360         static {
1361             SUMMERS = new ConcurrentHashMap<>();
1362             Lookup publicLookup = MethodHandles.publicLookup();
1363             NEW_STRING_BUILDER = lookupConstructor(publicLookup, StringBuilder.class, int.class);
1364             STRING_LENGTH = lookupVirtual(publicLookup, String.class, "length", int.class);
1365             BUILDER_TO_STRING = lookupVirtual(publicLookup, StringBuilder.class, "toString", String.class);
1366             if (DEBUG) {
1367                 BUILDER_TO_STRING_CHECKED = lookupStatic(MethodHandles.Lookup.IMPL_LOOKUP,
1368                         MethodHandleStringBuilderStrategy.class, "toStringChecked", String.class, StringBuilder.class);
1369             } else {
1370                 BUILDER_TO_STRING_CHECKED = null;
1371             }
1372         }
1373 
1374     }
1375 
1376 
1377     /**
1378      * <p><b>{@link Strategy#MH_INLINE_SIZED_EXACT}: "MethodHandles inline,
1379      * sized exactly".</b>
1380      *
1381      * <p>This strategy replicates what StringBuilders are doing: it builds the
1382      * byte[] array on its own and passes that byte[] array to String
1383      * constructor. This strategy requires access to some private APIs in JDK,
1384      * most notably, the read-only Integer/Long.stringSize methods that measure
1385      * the character length of the integers, and the private String constructor
1386      * that accepts byte[] arrays without copying. While this strategy assumes a
1387      * particular implementation details for String, this opens the door for
1388      * building a very optimal concatenation sequence. This is the only strategy
1389      * that requires porting if there are private JDK changes occur.
1390      */
1391     private static final class MethodHandleInlineCopyStrategy {
1392 
1393         private MethodHandleInlineCopyStrategy() {
1394             // no instantiation
1395         }
1396 
1397         static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable {
1398 
1399             // Create filters and obtain filtered parameter types. Filters would be used in the beginning
1400             // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings).
1401             // The filtered argument type list is used all over in the combinators below.
1402             Class<?>[] ptypes = mt.parameterArray();
1403             MethodHandle[] filters = null;
1404             for (int i = 0; i < ptypes.length; i++) {
1405                 MethodHandle filter = Stringifiers.forMost(ptypes[i]);
1406                 if (filter != null) {
1407                     if (filters == null) {
1408                         filters = new MethodHandle[ptypes.length];
1409                     }
1410                     filters[i] = filter;
1411                     ptypes[i] = filter.type().returnType();
1412                 }
1413             }
1414             List<Class<?>> ptypesList = Arrays.asList(ptypes);
1415 
1416             // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"
1417             // with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up,
1418             // which makes the code arguably hard to read.
1419 
1420             // Drop all remaining parameter types, leave only helper arguments:
1421             MethodHandle mh;
1422 
1423             mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
1424             mh = MethodHandles.dropArguments(mh, 0, int.class);
1425 
1426             // In debug mode, check that remaining index is zero.
1427             if (DEBUG) {
1428                 mh = MethodHandles.filterArgument(mh, 0, CHECK_INDEX);
1429             }
1430 
1431             // Mix in prependers. This happens when (int, byte[], byte) = (index, storage, coder) is already
1432             // known from the combinators below. We are assembling the string backwards, so "index" is the
1433             // *ending* index.
1434             for (RecipeElement el : recipe.getElements()) {
1435                 MethodHandle prepender;
1436                 switch (el.getTag()) {
1437                     case CONST: {
1438                         Object cnst = el.getValue();
1439                         prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst);
1440                         break;
1441                     }
1442                     case ARG: {
1443                         int pos = el.getArgPos();
1444                         prepender = selectArgument(prepender(ptypesList.get(pos)), 3, ptypesList, pos);
1445                         break;
1446                     }
1447                     default:
1448                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1449                 }
1450 
1451                 // Remove "old" index from arguments
1452                 mh = MethodHandles.dropArguments(mh, 1, int.class);
1453 
1454                 // Do the prepend, and put "new" index at index 0
1455                 mh = MethodHandles.foldArguments(mh, prepender);
1456             }
1457 
1458             // Prepare the argument list for prepending. The tree below would instantiate
1459             // the storage byte[] into argument 0, so we need to swap "storage" and "index".
1460             // The index at this point equals to "size", and resides at argument 1.
1461             {
1462                 MethodType nmt = mh.type()
1463                         .changeParameterType(0, byte[].class)
1464                         .changeParameterType(1, int.class);
1465                 mh = MethodHandles.permuteArguments(mh, nmt, swap10(nmt.parameterCount()));
1466             }
1467 
1468             // Fold in byte[] instantiation at argument 0.
1469             MethodHandle combiner = MethodHandles.dropArguments(NEW_ARRAY, 2, ptypesList);
1470             mh = MethodHandles.foldArguments(mh, combiner);
1471 
1472             // Start combining length and coder mixers.
1473             //
1474             // Length is easy: constant lengths can be computed on the spot, and all non-constant
1475             // shapes have been either converted to Strings, or explicit methods for getting the
1476             // string length out of primitives are provided.
1477             //
1478             // Coders are more interesting. Only Object, String and char arguments (and constants)
1479             // can have non-Latin1 encoding. It is easier to blindly convert constants to String,
1480             // and deduce the coder from there. Arguments would be either converted to Strings
1481             // during the initial filtering, or handled by primitive specializations in CODER_MIXERS.
1482             //
1483             // The method handle shape after all length and coder mixers is:
1484             //   (int, byte, <args>)String = ("index", "coder", <args>)
1485             byte initialCoder = 0; // initial coder
1486             int initialLen = 0;    // initial length, in characters
1487             for (RecipeElement el : recipe.getElements()) {
1488                 switch (el.getTag()) {
1489                     case CONST: {
1490                         Object constant = el.getValue();
1491                         String s = constant.toString();
1492                         initialCoder = (byte) coderMixer(String.class).invoke(initialCoder, s);
1493                         initialLen += s.length();
1494                         break;
1495                     }
1496                     case ARG: {
1497                         int ac = el.getArgPos();
1498 
1499                         Class<?> argClass = ptypesList.get(ac);
1500                         MethodHandle lm = selectArgument(lengthMixer(argClass), 1, ptypesList, ac);
1501                         lm = MethodHandles.dropArguments(lm, 0, byte.class); // (*)
1502                         lm = MethodHandles.dropArguments(lm, 2, byte.class);
1503 
1504                         MethodHandle cm = selectArgument(coderMixer(argClass),  1, ptypesList, ac);
1505                         cm = MethodHandles.dropArguments(cm, 0, int.class);  // (**)
1506 
1507                         // Read this bottom up:
1508 
1509                         // 4. Drop old index and coder, producing ("new-index", "new-coder", <args>)
1510                         mh = MethodHandles.dropArguments(mh, 2, int.class, byte.class);
1511 
1512                         // 3. Compute "new-index", producing ("new-index", "new-coder", "old-index", "old-coder", <args>)
1513                         //    Length mixer ignores both "new-coder" and "old-coder" due to dropArguments above (*)
1514                         mh = MethodHandles.foldArguments(mh, lm);
1515 
1516                         // 2. Compute "new-coder", producing ("new-coder", "old-index", "old-coder", <args>)
1517                         //    Coder mixer ignores the "old-index" arg due to dropArguments above (**)
1518                         mh = MethodHandles.foldArguments(mh, cm);
1519 
1520                         // 1. The mh shape here is ("old-index", "old-coder", <args>)
1521                         break;
1522                     }
1523                     default:
1524                         throw new StringConcatException("Unhandled tag: " + el.getTag());
1525                 }
1526             }
1527 
1528             // Insert initial lengths and coders here.
1529             // The method handle shape here is (<args>).
1530             mh = MethodHandles.insertArguments(mh, 0, initialLen, initialCoder);
1531 
1532             // Apply filters, converting the arguments:
1533             if (filters != null) {
1534                 mh = MethodHandles.filterArguments(mh, 0, filters);
1535             }
1536 
1537             return mh;
1538         }
1539 
1540         private static int[] swap10(int count) {
1541             int[] perm = new int[count];
1542             perm[0] = 1;
1543             perm[1] = 0;
1544             for (int i = 2; i < count; i++) {
1545                 perm[i] = i;
1546             }
1547             return perm;
1548         }
1549 
1550         // Adapts: (...prefix..., parameter[pos])R -> (...prefix..., ...parameters...)R
1551         private static MethodHandle selectArgument(MethodHandle mh, int prefix, List<Class<?>> ptypes, int pos) {
1552             if (pos == 0) {
1553                 return MethodHandles.dropArguments(mh, prefix + 1, ptypes.subList(1, ptypes.size()));
1554             } else if (pos == ptypes.size() - 1) {
1555                 return MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, ptypes.size() - 1));
1556             } else { // 0 < pos < ptypes.size() - 1
1557                 MethodHandle t = MethodHandles.dropArguments(mh, prefix, ptypes.subList(0, pos));
1558                 return MethodHandles.dropArguments(t, prefix + 1 + pos, ptypes.subList(pos + 1, ptypes.size()));
1559             }
1560         }
1561 
1562         @ForceInline
1563         private static byte[] newArray(int length, byte coder) {
1564             return new byte[length << coder];
1565         }
1566 
1567         @ForceInline
1568         private static int checkIndex(int index) {
1569             if (index != 0) {
1570                 throw new AssertionError("Exactness check failed: " + index + " characters left in the buffer.");
1571             }
1572             return index;
1573         }
1574 
1575         private static MethodHandle prepender(Class<?> cl) {
1576             return PREPENDERS.computeIfAbsent(cl, PREPEND);
1577         }
1578 
1579         private static MethodHandle coderMixer(Class<?> cl) {
1580             return CODER_MIXERS.computeIfAbsent(cl, CODER_MIX);
1581         }
1582 
1583         private static MethodHandle lengthMixer(Class<?> cl) {
1584             return LENGTH_MIXERS.computeIfAbsent(cl, LENGTH_MIX);
1585         }
1586 
1587         // This one is deliberately non-lambdified to optimize startup time:
1588         private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() {
1589             @Override
1590             public MethodHandle apply(Class<?> c) {
1591                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", int.class, int.class, byte[].class, byte.class, c);
1592             }
1593         };
1594 
1595         // This one is deliberately non-lambdified to optimize startup time:
1596         private static final Function<Class<?>, MethodHandle> CODER_MIX = new Function<Class<?>, MethodHandle>() {
1597             @Override
1598             public MethodHandle apply(Class<?> c) {
1599                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mixCoder", byte.class, byte.class, c);
1600             }
1601         };
1602 
1603         // This one is deliberately non-lambdified to optimize startup time:
1604         private static final Function<Class<?>, MethodHandle> LENGTH_MIX = new Function<Class<?>, MethodHandle>() {
1605             @Override
1606             public MethodHandle apply(Class<?> c) {
1607                 return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mixLen", int.class, int.class, c);
1608             }
1609         };
1610 
1611         private static final MethodHandle NEW_STRING;
1612         private static final MethodHandle CHECK_INDEX;
1613         private static final MethodHandle NEW_ARRAY;
1614         private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS;
1615         private static final ConcurrentMap<Class<?>, MethodHandle> LENGTH_MIXERS;
1616         private static final ConcurrentMap<Class<?>, MethodHandle> CODER_MIXERS;
1617         private static final Class<?> STRING_HELPER;
1618 
1619         static {
1620             try {
1621                 STRING_HELPER = Class.forName("java.lang.StringConcatHelper");
1622             } catch (ClassNotFoundException e) {
1623                 throw new AssertionError(e);
1624             }
1625 
1626             PREPENDERS = new ConcurrentHashMap<>();
1627             LENGTH_MIXERS = new ConcurrentHashMap<>();
1628             CODER_MIXERS = new ConcurrentHashMap<>();
1629 
1630             NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, byte.class);
1631             NEW_ARRAY  = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "newArray", byte[].class, int.class, byte.class);
1632 
1633             if (DEBUG) {
1634                 CHECK_INDEX = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "checkIndex", int.class, int.class);
1635             } else {
1636                 CHECK_INDEX = null;
1637             }
1638         }
1639     }
1640 
1641     /**
1642      * Public gateways to public "stringify" methods. These methods have the form String apply(T obj), and normally
1643      * delegate to {@code String.valueOf}, depending on argument's type.
1644      */
1645     private static final class Stringifiers {
1646         private Stringifiers() {
1647             // no instantiation
1648         }
1649 
1650         // This one is deliberately non-lambdified to optimize startup time:
1651         private static final Function<Class<?>, MethodHandle> MOST = new Function<Class<?>, MethodHandle>() {
1652             @Override
1653             public MethodHandle apply(Class<?> cl) {
1654                 MethodHandle mhObject = lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, Object.class);
1655 
1656                 // We need the additional conversion here, because String.valueOf(Object) may return null.
1657                 // String conversion rules in Java state we need to produce "null" String in this case.
1658                 // It can be easily done with applying valueOf the second time.
1659                 MethodHandle mhObjectNoNulls = MethodHandles.filterReturnValue(mhObject,
1660                         mhObject.asType(MethodType.methodType(String.class, String.class)));
1661 
1662                 if (cl == String.class) {
1663                     return mhObject;
1664                 } else if (cl == float.class) {
1665                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, float.class);
1666                 } else if (cl == double.class) {
1667                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, double.class);
1668                 } else if (!cl.isPrimitive()) {
1669                     return mhObjectNoNulls;
1670                 }
1671 
1672                 return null;
1673             }
1674         };
1675 
1676         // This one is deliberately non-lambdified to optimize startup time:
1677         private static final Function<Class<?>, MethodHandle> ANY = new Function<Class<?>, MethodHandle>() {
1678             @Override
1679             public MethodHandle apply(Class<?> cl) {
1680                 MethodHandle mh = MOST.apply(cl);
1681                 if (mh != null) {
1682                     return mh;
1683                 }
1684 
1685                 if (cl == byte.class || cl == short.class || cl == int.class) {
1686                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, int.class);
1687                 } else if (cl == boolean.class) {
1688                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, boolean.class);
1689                 } else if (cl == char.class) {
1690                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, char.class);
1691                 } else if (cl == long.class) {
1692                     return lookupStatic(Lookup.PUBLIC_LOOKUP, String.class, "valueOf", String.class, long.class);
1693                 } else {
1694                     throw new IllegalStateException("Unknown class: " + cl);
1695                 }
1696             }
1697         };
1698 
1699         private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_MOST = new ConcurrentHashMap<>();
1700         private static final ConcurrentMap<Class<?>, MethodHandle> STRINGIFIERS_ANY = new ConcurrentHashMap<>();
1701 
1702         /**
1703          * Returns a stringifier for references and floats/doubles only.
1704          * Always returns null for other primitives.
1705          *
1706          * @param t class to stringify
1707          * @return stringifier; null, if not available
1708          */
1709         static MethodHandle forMost(Class<?> t) {
1710             return STRINGIFIERS_MOST.computeIfAbsent(t, MOST);
1711         }
1712 
1713         /**
1714          * Returns a stringifier for any type. Never returns null.
1715          *
1716          * @param t class to stringify
1717          * @return stringifier
1718          */
1719         static MethodHandle forAny(Class<?> t) {
1720             return STRINGIFIERS_ANY.computeIfAbsent(t, ANY);
1721         }
1722     }
1723 
1724     /* ------------------------------- Common utilities ------------------------------------ */
1725 
1726     private static MethodHandle lookupStatic(Lookup lookup, Class<?> refc, String name, Class<?> rtype, Class<?>... ptypes) {
1727         try {
1728             return lookup.findStatic(refc, name, MethodType.methodType(rtype, ptypes));
1729         } catch (NoSuchMethodException | IllegalAccessException e) {
1730             throw new AssertionError(e);
1731         }
1732     }
1733 
1734     private static MethodHandle lookupVirtual(Lookup lookup, Class<?> refc, String name, Class<?> rtype, Class<?>... ptypes) {
1735         try {
1736             return lookup.findVirtual(refc, name, MethodType.methodType(rtype, ptypes));
1737         } catch (NoSuchMethodException | IllegalAccessException e) {
1738             throw new AssertionError(e);
1739         }
1740     }
1741 
1742     private static MethodHandle lookupConstructor(Lookup lookup, Class<?> refc, Class<?> ptypes) {
1743         try {
1744             return lookup.findConstructor(refc, MethodType.methodType(void.class, ptypes));
1745         } catch (NoSuchMethodException | IllegalAccessException e) {
1746             throw new AssertionError(e);
1747         }
1748     }
1749 
1750     private static int estimateSize(Class<?> cl) {
1751         if (cl == Integer.TYPE) {
1752             return 11; // "-2147483648"
1753         } else if (cl == Boolean.TYPE) {
1754             return 5; // "false"
1755         } else if (cl == Byte.TYPE) {
1756             return 4; // "-128"
1757         } else if (cl == Character.TYPE) {
1758             return 1; // duh
1759         } else if (cl == Short.TYPE) {
1760             return 6; // "-32768"
1761         } else if (cl == Double.TYPE) {
1762             return 26; // apparently, no larger than this, see FloatingDecimal.BinaryToASCIIBuffer.buffer
1763         } else if (cl == Float.TYPE) {
1764             return 26; // apparently, no larger than this, see FloatingDecimal.BinaryToASCIIBuffer.buffer
1765         } else if (cl == Long.TYPE)  {
1766             return 20; // "-9223372036854775808"
1767         } else {
1768             throw new IllegalArgumentException("Cannot estimate the size for " + cl);
1769         }
1770     }
1771 
1772     private static Class<?> adaptToStringBuilder(Class<?> c) {
1773         if (c.isPrimitive()) {
1774             if (c == Byte.TYPE || c == Short.TYPE) {
1775                 return int.class;
1776             }
1777         } else {
1778             if (c != String.class) {
1779                 return Object.class;
1780             }
1781         }
1782         return c;
1783     }
1784 
1785     private StringConcatFactory() {
1786         // no instantiation
1787     }
1788 
1789 }