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