| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
package java.lang.invoke;  | 
 | 
 | 
 | 
import jdk.internal.misc.Unsafe;  | 
 | 
import jdk.internal.org.objectweb.asm.ClassWriter;  | 
 | 
import jdk.internal.org.objectweb.asm.Label;  | 
 | 
import jdk.internal.org.objectweb.asm.MethodVisitor;  | 
 | 
import jdk.internal.org.objectweb.asm.Opcodes;  | 
 | 
import jdk.internal.vm.annotation.ForceInline;  | 
 | 
import sun.invoke.util.Wrapper;  | 
 | 
import sun.security.action.GetPropertyAction;  | 
 | 
 | 
 | 
import java.lang.invoke.MethodHandles.Lookup;  | 
 | 
import java.util.ArrayList;  | 
 | 
import java.util.Arrays;  | 
 | 
import java.util.List;  | 
 | 
import java.util.Objects;  | 
 | 
import java.util.Properties;  | 
 | 
import java.util.concurrent.ConcurrentHashMap;  | 
 | 
import java.util.concurrent.ConcurrentMap;  | 
 | 
import java.util.function.Function;  | 
 | 
 | 
 | 
import static jdk.internal.org.objectweb.asm.Opcodes.*;  | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
public final class StringConcatFactory { | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private static final char TAG_ARG = '\u0001';  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private static final char TAG_CONST = '\u0002';  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200;  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static Strategy STRATEGY;  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private static final Strategy DEFAULT_STRATEGY = Strategy.MH_INLINE_SIZED_EXACT;  | 
 | 
 | 
 | 
    private enum Strategy { | 
 | 
          | 
 | 
 | 
 | 
         */  | 
 | 
        BC_SB,  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        BC_SB_SIZED,  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        BC_SB_SIZED_EXACT,  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        MH_SB_SIZED,  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        MH_SB_SIZED_EXACT,  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        MH_INLINE_SIZED_EXACT  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final boolean DEBUG;  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final boolean CACHE_ENABLE;  | 
 | 
 | 
 | 
    private static final ConcurrentMap<Key, MethodHandle> CACHE;  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private static final ProxyClassesDumper DUMPER;  | 
 | 
 | 
 | 
    static { | 
 | 
        // In case we need to double-back onto the StringConcatFactory during this  | 
 | 
        // static initialization, make sure we have the reasonable defaults to complete  | 
 | 
        // the static initialization properly. After that, actual users would use  | 
 | 
          | 
 | 
        STRATEGY = DEFAULT_STRATEGY;  | 
 | 
        // CACHE_ENABLE = false; // implied  | 
 | 
        // CACHE = null;         // implied  | 
 | 
        // DEBUG = false;        // implied  | 
 | 
        // DUMPER = null;        // implied  | 
 | 
 | 
 | 
        Properties props = GetPropertyAction.privilegedGetProperties();  | 
 | 
        final String strategy =  | 
 | 
                props.getProperty("java.lang.invoke.stringConcat"); | 
 | 
        CACHE_ENABLE = Boolean.parseBoolean(  | 
 | 
                props.getProperty("java.lang.invoke.stringConcat.cache")); | 
 | 
        DEBUG = Boolean.parseBoolean(  | 
 | 
                props.getProperty("java.lang.invoke.stringConcat.debug")); | 
 | 
        final String dumpPath =  | 
 | 
                props.getProperty("java.lang.invoke.stringConcat.dumpClasses"); | 
 | 
 | 
 | 
        STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy);  | 
 | 
        CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null;  | 
 | 
        DUMPER = (dumpPath == null) ? null : ProxyClassesDumper.getInstance(dumpPath);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final class Key { | 
 | 
        final String className;  | 
 | 
        final MethodType mt;  | 
 | 
        final Recipe recipe;  | 
 | 
 | 
 | 
        public Key(String className, MethodType mt, Recipe recipe) { | 
 | 
            this.className = className;  | 
 | 
            this.mt = mt;  | 
 | 
            this.recipe = recipe;  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public boolean equals(Object o) { | 
 | 
            if (this == o) return true;  | 
 | 
            if (o == null || getClass() != o.getClass()) return false;  | 
 | 
 | 
 | 
            Key key = (Key) o;  | 
 | 
 | 
 | 
            if (!className.equals(key.className)) return false;  | 
 | 
            if (!mt.equals(key.mt)) return false;  | 
 | 
            if (!recipe.equals(key.recipe)) return false;  | 
 | 
            return true;  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public int hashCode() { | 
 | 
            int result = className.hashCode();  | 
 | 
            result = 31 * result + mt.hashCode();  | 
 | 
            result = 31 * result + recipe.hashCode();  | 
 | 
            return result;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final class Recipe { | 
 | 
        private final List<RecipeElement> elements;  | 
 | 
 | 
 | 
        public Recipe(String src, Object[] constants) { | 
 | 
            List<RecipeElement> el = new ArrayList<>();  | 
 | 
 | 
 | 
            int constC = 0;  | 
 | 
            int argC = 0;  | 
 | 
 | 
 | 
            StringBuilder acc = new StringBuilder();  | 
 | 
 | 
 | 
            for (int i = 0; i < src.length(); i++) { | 
 | 
                char c = src.charAt(i);  | 
 | 
 | 
 | 
                if (c == TAG_CONST || c == TAG_ARG) { | 
 | 
                    // Detected a special tag, flush all accumulated characters  | 
 | 
                      | 
 | 
                    if (acc.length() > 0) { | 
 | 
                        el.add(new RecipeElement(acc.toString()));  | 
 | 
                        acc.setLength(0);  | 
 | 
                    }  | 
 | 
                    if (c == TAG_CONST) { | 
 | 
                        Object cnst = constants[constC++];  | 
 | 
                        el.add(new RecipeElement(cnst));  | 
 | 
                    } else if (c == TAG_ARG) { | 
 | 
                        el.add(new RecipeElement(argC++));  | 
 | 
                    }  | 
 | 
                } else { | 
 | 
                    // Not a special character, this is a constant embedded into  | 
 | 
                      | 
 | 
                    acc.append(c);  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            if (acc.length() > 0) { | 
 | 
                el.add(new RecipeElement(acc.toString()));  | 
 | 
            }  | 
 | 
 | 
 | 
            elements = el;  | 
 | 
        }  | 
 | 
 | 
 | 
        public List<RecipeElement> getElements() { | 
 | 
            return elements;  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public boolean equals(Object o) { | 
 | 
            if (this == o) return true;  | 
 | 
            if (o == null || getClass() != o.getClass()) return false;  | 
 | 
 | 
 | 
            Recipe recipe = (Recipe) o;  | 
 | 
            return elements.equals(recipe.elements);  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public int hashCode() { | 
 | 
            return elements.hashCode();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private static final class RecipeElement { | 
 | 
        private final String value;  | 
 | 
        private final int argPos;  | 
 | 
        private final char tag;  | 
 | 
 | 
 | 
        public RecipeElement(Object cnst) { | 
 | 
            this.value = String.valueOf(Objects.requireNonNull(cnst));  | 
 | 
            this.argPos = -1;  | 
 | 
            this.tag = TAG_CONST;  | 
 | 
        }  | 
 | 
 | 
 | 
        public RecipeElement(int arg) { | 
 | 
            this.value = null;  | 
 | 
            this.argPos = arg;  | 
 | 
            this.tag = TAG_ARG;  | 
 | 
        }  | 
 | 
 | 
 | 
        public String getValue() { | 
 | 
            assert (tag == TAG_CONST);  | 
 | 
            return value;  | 
 | 
        }  | 
 | 
 | 
 | 
        public int getArgPos() { | 
 | 
            assert (tag == TAG_ARG);  | 
 | 
            return argPos;  | 
 | 
        }  | 
 | 
 | 
 | 
        public char getTag() { | 
 | 
            return tag;  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public boolean equals(Object o) { | 
 | 
            if (this == o) return true;  | 
 | 
            if (o == null || getClass() != o.getClass()) return false;  | 
 | 
 | 
 | 
            RecipeElement that = (RecipeElement) o;  | 
 | 
 | 
 | 
            if (this.tag != that.tag) return false;  | 
 | 
            if (this.tag == TAG_CONST && (!value.equals(that.value))) return false;  | 
 | 
            if (this.tag == TAG_ARG && (argPos != that.argPos)) return false;  | 
 | 
            return true;  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public int hashCode() { | 
 | 
            return (int)tag;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    // StringConcatFactory bootstrap methods are startup sensitive, and may be  | 
 | 
    // special cased in java.lang.invokeBootstrapMethodInvoker to ensure  | 
 | 
    // methods are invoked with exact type information to avoid generating  | 
 | 
    // code for runtime checks. Take care any changes or additions here are  | 
 | 
    // reflected there as appropriate.  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    public static CallSite makeConcat(MethodHandles.Lookup lookup,  | 
 | 
                                      String name,  | 
 | 
                                      MethodType concatType) throws StringConcatException { | 
 | 
        if (DEBUG) { | 
 | 
            System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType); | 
 | 
        }  | 
 | 
 | 
 | 
        return doStringConcat(lookup, name, concatType, true, null);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    public static CallSite makeConcatWithConstants(MethodHandles.Lookup lookup,  | 
 | 
                                                   String name,  | 
 | 
                                                   MethodType concatType,  | 
 | 
                                                   String recipe,  | 
 | 
                                                   Object... constants) throws StringConcatException { | 
 | 
        if (DEBUG) { | 
 | 
            System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType + ", {" + recipe + "}, " + Arrays.toString(constants)); | 
 | 
        }  | 
 | 
 | 
 | 
        return doStringConcat(lookup, name, concatType, false, recipe, constants);  | 
 | 
    }  | 
 | 
 | 
 | 
    private static CallSite doStringConcat(MethodHandles.Lookup lookup,  | 
 | 
                                           String name,  | 
 | 
                                           MethodType concatType,  | 
 | 
                                           boolean generateRecipe,  | 
 | 
                                           String recipe,  | 
 | 
                                           Object... constants) throws StringConcatException { | 
 | 
        Objects.requireNonNull(lookup, "Lookup is null");  | 
 | 
        Objects.requireNonNull(name, "Name is null");  | 
 | 
        Objects.requireNonNull(concatType, "Concat type is null");  | 
 | 
        Objects.requireNonNull(constants, "Constants are null");  | 
 | 
 | 
 | 
        for (Object o : constants) { | 
 | 
            Objects.requireNonNull(o, "Cannot accept null constants");  | 
 | 
        }  | 
 | 
 | 
 | 
        if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { | 
 | 
            throw new StringConcatException("Invalid caller: " + | 
 | 
                    lookup.lookupClass().getName());  | 
 | 
        }  | 
 | 
 | 
 | 
        int cCount = 0;  | 
 | 
        int oCount = 0;  | 
 | 
        if (generateRecipe) { | 
 | 
              | 
 | 
            char[] value = new char[concatType.parameterCount()];  | 
 | 
            Arrays.fill(value, TAG_ARG);  | 
 | 
            recipe = new String(value);  | 
 | 
            oCount = concatType.parameterCount();  | 
 | 
        } else { | 
 | 
            Objects.requireNonNull(recipe, "Recipe is null");  | 
 | 
 | 
 | 
            for (int i = 0; i < recipe.length(); i++) { | 
 | 
                char c = recipe.charAt(i);  | 
 | 
                if (c == TAG_CONST) cCount++;  | 
 | 
                if (c == TAG_ARG)   oCount++;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (oCount != concatType.parameterCount()) { | 
 | 
            throw new StringConcatException(  | 
 | 
                    "Mismatched number of concat arguments: recipe wants " +  | 
 | 
                            oCount +  | 
 | 
                            " arguments, but signature provides " +  | 
 | 
                            concatType.parameterCount());  | 
 | 
        }  | 
 | 
 | 
 | 
        if (cCount != constants.length) { | 
 | 
            throw new StringConcatException(  | 
 | 
                    "Mismatched number of concat constants: recipe wants " +  | 
 | 
                            cCount +  | 
 | 
                            " constants, but only " +  | 
 | 
                            constants.length +  | 
 | 
                            " are passed");  | 
 | 
        }  | 
 | 
 | 
 | 
        if (!concatType.returnType().isAssignableFrom(String.class)) { | 
 | 
            throw new StringConcatException(  | 
 | 
                    "The return type should be compatible with String, but it is " +  | 
 | 
                            concatType.returnType());  | 
 | 
        }  | 
 | 
 | 
 | 
        if (concatType.parameterSlotCount() > MAX_INDY_CONCAT_ARG_SLOTS) { | 
 | 
            throw new StringConcatException("Too many concat argument slots: " + | 
 | 
                    concatType.parameterSlotCount() +  | 
 | 
                    ", can only accept " +  | 
 | 
                    MAX_INDY_CONCAT_ARG_SLOTS);  | 
 | 
        }  | 
 | 
 | 
 | 
        String className = getClassName(lookup.lookupClass());  | 
 | 
        MethodType mt = adaptType(concatType);  | 
 | 
        Recipe rec = new Recipe(recipe, constants);  | 
 | 
 | 
 | 
        MethodHandle mh;  | 
 | 
        if (CACHE_ENABLE) { | 
 | 
            Key key = new Key(className, mt, rec);  | 
 | 
            mh = CACHE.get(key);  | 
 | 
            if (mh == null) { | 
 | 
                mh = generate(lookup, className, mt, rec);  | 
 | 
                CACHE.put(key, mh);  | 
 | 
            }  | 
 | 
        } else { | 
 | 
            mh = generate(lookup, className, mt, rec);  | 
 | 
        }  | 
 | 
        return new ConstantCallSite(mh.asType(concatType));  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static MethodType adaptType(MethodType args) { | 
 | 
        Class<?>[] ptypes = null;  | 
 | 
        for (int i = 0; i < args.parameterCount(); i++) { | 
 | 
            Class<?> ptype = args.parameterType(i);  | 
 | 
            if (!ptype.isPrimitive() &&  | 
 | 
                    ptype != String.class &&  | 
 | 
                    ptype != Object.class) {  | 
 | 
                if (ptypes == null) { | 
 | 
                    ptypes = args.parameterArray();  | 
 | 
                }  | 
 | 
                ptypes[i] = Object.class;  | 
 | 
            }  | 
 | 
            // else other primitives or String or Object (unchanged)  | 
 | 
        }  | 
 | 
        return (ptypes != null)  | 
 | 
                ? MethodType.methodType(args.returnType(), ptypes)  | 
 | 
                : args;  | 
 | 
    }  | 
 | 
 | 
 | 
    private static String getClassName(Class<?> hostClass) throws StringConcatException { | 
 | 
        /*  | 
 | 
          When cache is enabled, we want to cache as much as we can.  | 
 | 
 | 
 | 
          However, there are two peculiarities:  | 
 | 
 | 
 | 
           a) The generated class should stay within the same package as the  | 
 | 
              host class, to allow Unsafe.defineAnonymousClass access controls  | 
 | 
              to work properly. JDK may choose to fail with IllegalAccessException  | 
 | 
              when accessing a VM anonymous class with non-privileged callers,  | 
 | 
              see JDK-8058575.  | 
 | 
 | 
 | 
           b) If we mark the stub with some prefix, say, derived from the package  | 
 | 
              name because of (a), we can technically use that stub in other packages.  | 
 | 
              But the call stack traces would be extremely puzzling to unsuspecting users  | 
 | 
              and profiling tools: whatever stub wins the race, would be linked in all  | 
 | 
              similar callsites.  | 
 | 
 | 
 | 
           Therefore, we set the class prefix to match the host class package, and use  | 
 | 
           the prefix as the cache key too. This only affects BC_* strategies, and only when  | 
 | 
           cache is enabled.  | 
 | 
         */  | 
 | 
 | 
 | 
        switch (STRATEGY) { | 
 | 
            case BC_SB:  | 
 | 
            case BC_SB_SIZED:  | 
 | 
            case BC_SB_SIZED_EXACT: { | 
 | 
                if (CACHE_ENABLE) { | 
 | 
                    String pkgName = hostClass.getPackageName();  | 
 | 
                    return (pkgName != null && !pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat"; | 
 | 
                } else { | 
 | 
                    return hostClass.getName().replace('.', '/') + "$$StringConcat"; | 
 | 
                }  | 
 | 
            }  | 
 | 
            case MH_SB_SIZED:  | 
 | 
            case MH_SB_SIZED_EXACT:  | 
 | 
            case MH_INLINE_SIZED_EXACT:  | 
 | 
                  | 
 | 
                return "";  | 
 | 
            default:  | 
 | 
                throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented"); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private static MethodHandle generate(Lookup lookup, String className, MethodType mt, Recipe recipe) throws StringConcatException { | 
 | 
        try { | 
 | 
            switch (STRATEGY) { | 
 | 
                case BC_SB:  | 
 | 
                    return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.DEFAULT);  | 
 | 
                case BC_SB_SIZED:  | 
 | 
                    return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED);  | 
 | 
                case BC_SB_SIZED_EXACT:  | 
 | 
                    return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED_EXACT);  | 
 | 
                case MH_SB_SIZED:  | 
 | 
                    return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED);  | 
 | 
                case MH_SB_SIZED_EXACT:  | 
 | 
                    return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT);  | 
 | 
                case MH_INLINE_SIZED_EXACT:  | 
 | 
                    return MethodHandleInlineCopyStrategy.generate(mt, recipe);  | 
 | 
                default:  | 
 | 
                    throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented"); | 
 | 
            }  | 
 | 
        } catch (Error | StringConcatException e) { | 
 | 
              | 
 | 
            throw e;  | 
 | 
        } catch (Throwable t) { | 
 | 
            throw new StringConcatException("Generator failed", t); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private enum Mode { | 
 | 
        DEFAULT(false, false),  | 
 | 
        SIZED(true, false),  | 
 | 
        SIZED_EXACT(true, true);  | 
 | 
 | 
 | 
        private final boolean sized;  | 
 | 
        private final boolean exact;  | 
 | 
 | 
 | 
        Mode(boolean sized, boolean exact) { | 
 | 
            this.sized = sized;  | 
 | 
            this.exact = exact;  | 
 | 
        }  | 
 | 
 | 
 | 
        boolean isSized() { | 
 | 
            return sized;  | 
 | 
        }  | 
 | 
 | 
 | 
        boolean isExact() { | 
 | 
            return exact;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final class BytecodeStringBuilderStrategy { | 
 | 
        static final Unsafe UNSAFE = Unsafe.getUnsafe();  | 
 | 
        static final int CLASSFILE_VERSION = 52;  | 
 | 
        static final String METHOD_NAME = "concat";  | 
 | 
 | 
 | 
        private BytecodeStringBuilderStrategy() { | 
 | 
            // no instantiation  | 
 | 
        }  | 
 | 
 | 
 | 
        private static MethodHandle generate(Lookup lookup, String className, MethodType args, Recipe recipe, Mode mode) throws Exception { | 
 | 
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);  | 
 | 
 | 
 | 
            cw.visit(CLASSFILE_VERSION,  | 
 | 
                    ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,  | 
 | 
                    className,    | 
 | 
                    null,  | 
 | 
                    "java/lang/Object",  | 
 | 
                    null  | 
 | 
            );  | 
 | 
 | 
 | 
            MethodVisitor mv = cw.visitMethod(  | 
 | 
                    ACC_PUBLIC + ACC_STATIC + ACC_FINAL,  | 
 | 
                    METHOD_NAME,  | 
 | 
                    args.toMethodDescriptorString(),  | 
 | 
                    null,  | 
 | 
                    null);  | 
 | 
 | 
 | 
            mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true); | 
 | 
            mv.visitCode();  | 
 | 
 | 
 | 
            Class<?>[] arr = args.parameterArray();  | 
 | 
            boolean[] guaranteedNonNull = new boolean[arr.length];  | 
 | 
 | 
 | 
            if (mode.isExact()) { | 
 | 
                /*  | 
 | 
                    In exact mode, we need to convert all arguments to their String representations,  | 
 | 
                    as this allows to compute their String sizes exactly. We cannot use private  | 
 | 
                    methods for primitives in here, therefore we need to convert those as well.  | 
 | 
 | 
 | 
                    We also record what arguments are guaranteed to be non-null as the result  | 
 | 
                    of the conversion. String.valueOf does the null checks for us. The only  | 
 | 
                    corner case to take care of is String.valueOf(Object) returning null itself.  | 
 | 
 | 
 | 
                    Also, if any conversion happened, then the slot indices in the incoming  | 
 | 
                    arguments are not equal to the final local maps. The only case this may break  | 
 | 
                    is when converting 2-slot long/double argument to 1-slot String. Therefore,  | 
 | 
                    we get away with tracking modified offset, since no conversion can overwrite  | 
 | 
                    the upcoming the argument.  | 
 | 
                 */  | 
 | 
 | 
 | 
                int off = 0;  | 
 | 
                int modOff = 0;  | 
 | 
                for (int c = 0; c < arr.length; c++) { | 
 | 
                    Class<?> cl = arr[c];  | 
 | 
                    if (cl == String.class) { | 
 | 
                        if (off != modOff) { | 
 | 
                            mv.visitIntInsn(getLoadOpcode(cl), off);  | 
 | 
                            mv.visitIntInsn(ASTORE, modOff);  | 
 | 
                        }  | 
 | 
                    } else { | 
 | 
                        mv.visitIntInsn(getLoadOpcode(cl), off);  | 
 | 
                        mv.visitMethodInsn(  | 
 | 
                                INVOKESTATIC,  | 
 | 
                                "java/lang/String",  | 
 | 
                                "valueOf",  | 
 | 
                                getStringValueOfDesc(cl),  | 
 | 
                                false  | 
 | 
                        );  | 
 | 
                        mv.visitIntInsn(ASTORE, modOff);  | 
 | 
                        arr[c] = String.class;  | 
 | 
                        guaranteedNonNull[c] = cl.isPrimitive();  | 
 | 
                    }  | 
 | 
                    off += getParameterSize(cl);  | 
 | 
                    modOff += getParameterSize(String.class);  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            if (mode.isSized()) { | 
 | 
                /*  | 
 | 
                    When operating in sized mode (this includes exact mode), it makes sense to make  | 
 | 
                    StringBuilder append chains look familiar to OptimizeStringConcat. For that, we  | 
 | 
                    need to do null-checks early, not make the append chain shape simpler.  | 
 | 
                 */  | 
 | 
 | 
 | 
                int off = 0;  | 
 | 
                for (RecipeElement el : recipe.getElements()) { | 
 | 
                    switch (el.getTag()) { | 
 | 
                        case TAG_CONST:  | 
 | 
                              | 
 | 
                            break;  | 
 | 
                        case TAG_ARG:  | 
 | 
                            // Null-checks are needed only for String arguments, and when a previous stage  | 
 | 
                            // did not do implicit null-checks. If a String is null, we eagerly replace it  | 
 | 
                            // with "null" constant. Note, we omit Objects here, because we don't call  | 
 | 
                              | 
 | 
                            int ac = el.getArgPos();  | 
 | 
                            Class<?> cl = arr[ac];  | 
 | 
                            if (cl == String.class && !guaranteedNonNull[ac]) { | 
 | 
                                Label l0 = new Label();  | 
 | 
                                mv.visitIntInsn(ALOAD, off);  | 
 | 
                                mv.visitJumpInsn(IFNONNULL, l0);  | 
 | 
                                mv.visitLdcInsn("null"); | 
 | 
                                mv.visitIntInsn(ASTORE, off);  | 
 | 
                                mv.visitLabel(l0);  | 
 | 
                            }  | 
 | 
                            off += getParameterSize(cl);  | 
 | 
                            break;  | 
 | 
                        default:  | 
 | 
                            throw new StringConcatException("Unhandled tag: " + el.getTag()); | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");  | 
 | 
            mv.visitInsn(DUP);  | 
 | 
 | 
 | 
            if (mode.isSized()) { | 
 | 
                  | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
                 */  | 
 | 
                int len = 0;  | 
 | 
                int off = 0;  | 
 | 
 | 
 | 
                mv.visitInsn(ICONST_0);  | 
 | 
 | 
 | 
                for (RecipeElement el : recipe.getElements()) { | 
 | 
                    switch (el.getTag()) { | 
 | 
                        case TAG_CONST:  | 
 | 
                            len += el.getValue().length();  | 
 | 
                            break;  | 
 | 
                        case TAG_ARG:  | 
 | 
                              | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
                            */  | 
 | 
                            Class<?> cl = arr[el.getArgPos()];  | 
 | 
                            if (cl == String.class) { | 
 | 
                                mv.visitIntInsn(ALOAD, off);  | 
 | 
                                mv.visitMethodInsn(  | 
 | 
                                        INVOKEVIRTUAL,  | 
 | 
                                        "java/lang/String",  | 
 | 
                                        "length",  | 
 | 
                                        "()I",  | 
 | 
                                        false  | 
 | 
                                );  | 
 | 
                                mv.visitInsn(IADD);  | 
 | 
                            } else if (cl.isPrimitive()) { | 
 | 
                                len += estimateSize(cl);  | 
 | 
                            }  | 
 | 
                            off += getParameterSize(cl);  | 
 | 
                            break;  | 
 | 
                        default:  | 
 | 
                            throw new StringConcatException("Unhandled tag: " + el.getTag()); | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                  | 
 | 
                if (len > 0) { | 
 | 
                    iconst(mv, len);  | 
 | 
                    mv.visitInsn(IADD);  | 
 | 
                }  | 
 | 
 | 
 | 
                mv.visitMethodInsn(  | 
 | 
                        INVOKESPECIAL,  | 
 | 
                        "java/lang/StringBuilder",  | 
 | 
                        "<init>",  | 
 | 
                        "(I)V",  | 
 | 
                        false  | 
 | 
                );  | 
 | 
            } else { | 
 | 
                mv.visitMethodInsn(  | 
 | 
                        INVOKESPECIAL,  | 
 | 
                        "java/lang/StringBuilder",  | 
 | 
                        "<init>",  | 
 | 
                        "()V",  | 
 | 
                        false  | 
 | 
                );  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            { | 
 | 
                int off = 0;  | 
 | 
                for (RecipeElement el : recipe.getElements()) { | 
 | 
                    String desc;  | 
 | 
                    switch (el.getTag()) { | 
 | 
                        case TAG_CONST:  | 
 | 
                            mv.visitLdcInsn(el.getValue());  | 
 | 
                            desc = getSBAppendDesc(String.class);  | 
 | 
                            break;  | 
 | 
                        case TAG_ARG:  | 
 | 
                            Class<?> cl = arr[el.getArgPos()];  | 
 | 
                            mv.visitVarInsn(getLoadOpcode(cl), off);  | 
 | 
                            off += getParameterSize(cl);  | 
 | 
                            desc = getSBAppendDesc(cl);  | 
 | 
                            break;  | 
 | 
                        default:  | 
 | 
                            throw new StringConcatException("Unhandled tag: " + el.getTag()); | 
 | 
                    }  | 
 | 
 | 
 | 
                    mv.visitMethodInsn(  | 
 | 
                            INVOKEVIRTUAL,  | 
 | 
                            "java/lang/StringBuilder",  | 
 | 
                            "append",  | 
 | 
                            desc,  | 
 | 
                            false  | 
 | 
                    );  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            if (DEBUG && mode.isExact()) { | 
 | 
                /*  | 
 | 
                    Exactness checks compare the final StringBuilder.capacity() with a resulting  | 
 | 
                    String.length(). If these values disagree, that means StringBuilder had to perform  | 
 | 
                    storage trimming, which defeats the purpose of exact strategies.  | 
 | 
                 */  | 
 | 
 | 
 | 
                /*  | 
 | 
                   The logic for this check is as follows:  | 
 | 
 | 
 | 
                     Stack before:     Op:  | 
 | 
                      (SB)              dup, dup  | 
 | 
                      (SB, SB, SB)      capacity()  | 
 | 
                      (int, SB, SB)     swap  | 
 | 
                      (SB, int, SB)     toString()  | 
 | 
                      (S, int, SB)      length()  | 
 | 
                      (int, int, SB)    if_icmpeq  | 
 | 
                      (SB)              <end>  | 
 | 
 | 
 | 
                   Note that it leaves the same StringBuilder on exit, like the one on enter.  | 
 | 
                 */  | 
 | 
 | 
 | 
                mv.visitInsn(DUP);  | 
 | 
                mv.visitInsn(DUP);  | 
 | 
 | 
 | 
                mv.visitMethodInsn(  | 
 | 
                        INVOKEVIRTUAL,  | 
 | 
                        "java/lang/StringBuilder",  | 
 | 
                        "capacity",  | 
 | 
                        "()I",  | 
 | 
                        false  | 
 | 
                );  | 
 | 
 | 
 | 
                mv.visitInsn(SWAP);  | 
 | 
 | 
 | 
                mv.visitMethodInsn(  | 
 | 
                        INVOKEVIRTUAL,  | 
 | 
                        "java/lang/StringBuilder",  | 
 | 
                        "toString",  | 
 | 
                        "()Ljava/lang/String;",  | 
 | 
                        false  | 
 | 
                );  | 
 | 
 | 
 | 
                mv.visitMethodInsn(  | 
 | 
                        INVOKEVIRTUAL,  | 
 | 
                        "java/lang/String",  | 
 | 
                        "length",  | 
 | 
                        "()I",  | 
 | 
                        false  | 
 | 
                );  | 
 | 
 | 
 | 
                Label l0 = new Label();  | 
 | 
                mv.visitJumpInsn(IF_ICMPEQ, l0);  | 
 | 
 | 
 | 
                mv.visitTypeInsn(NEW, "java/lang/AssertionError");  | 
 | 
                mv.visitInsn(DUP);  | 
 | 
                mv.visitLdcInsn("Failed exactness check"); | 
 | 
                mv.visitMethodInsn(INVOKESPECIAL,  | 
 | 
                        "java/lang/AssertionError",  | 
 | 
                        "<init>",  | 
 | 
                        "(Ljava/lang/Object;)V",  | 
 | 
                        false);  | 
 | 
                mv.visitInsn(ATHROW);  | 
 | 
 | 
 | 
                mv.visitLabel(l0);  | 
 | 
            }  | 
 | 
 | 
 | 
            mv.visitMethodInsn(  | 
 | 
                    INVOKEVIRTUAL,  | 
 | 
                    "java/lang/StringBuilder",  | 
 | 
                    "toString",  | 
 | 
                    "()Ljava/lang/String;",  | 
 | 
                    false  | 
 | 
            );  | 
 | 
 | 
 | 
            mv.visitInsn(ARETURN);  | 
 | 
 | 
 | 
            mv.visitMaxs(-1, -1);  | 
 | 
            mv.visitEnd();  | 
 | 
            cw.visitEnd();  | 
 | 
 | 
 | 
            byte[] classBytes = cw.toByteArray();  | 
 | 
            try { | 
 | 
                Class<?> hostClass = lookup.lookupClass();  | 
 | 
                Class<?> innerClass = UNSAFE.defineAnonymousClass(hostClass, classBytes, null);  | 
 | 
                UNSAFE.ensureClassInitialized(innerClass);  | 
 | 
                dumpIfEnabled(innerClass.getName(), classBytes);  | 
 | 
                return Lookup.IMPL_LOOKUP.findStatic(innerClass, METHOD_NAME, args);  | 
 | 
            } catch (Exception e) { | 
 | 
                dumpIfEnabled(className + "$$FAILED", classBytes);  | 
 | 
                throw new StringConcatException("Exception while spinning the class", e); | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private static void dumpIfEnabled(String name, byte[] bytes) { | 
 | 
            if (DUMPER != null) { | 
 | 
                DUMPER.dumpClass(name, bytes);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private static String getSBAppendDesc(Class<?> cl) { | 
 | 
            if (cl.isPrimitive()) { | 
 | 
                if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { | 
 | 
                    return "(I)Ljava/lang/StringBuilder;";  | 
 | 
                } else if (cl == Boolean.TYPE) { | 
 | 
                    return "(Z)Ljava/lang/StringBuilder;";  | 
 | 
                } else if (cl == Character.TYPE) { | 
 | 
                    return "(C)Ljava/lang/StringBuilder;";  | 
 | 
                } else if (cl == Double.TYPE) { | 
 | 
                    return "(D)Ljava/lang/StringBuilder;";  | 
 | 
                } else if (cl == Float.TYPE) { | 
 | 
                    return "(F)Ljava/lang/StringBuilder;";  | 
 | 
                } else if (cl == Long.TYPE) { | 
 | 
                    return "(J)Ljava/lang/StringBuilder;";  | 
 | 
                } else { | 
 | 
                    throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl); | 
 | 
                }  | 
 | 
            } else if (cl == String.class) { | 
 | 
                return "(Ljava/lang/String;)Ljava/lang/StringBuilder;";  | 
 | 
            } else { | 
 | 
                return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;";  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private static String getStringValueOfDesc(Class<?> cl) { | 
 | 
            if (cl.isPrimitive()) { | 
 | 
                if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { | 
 | 
                    return "(I)Ljava/lang/String;";  | 
 | 
                } else if (cl == Boolean.TYPE) { | 
 | 
                    return "(Z)Ljava/lang/String;";  | 
 | 
                } else if (cl == Character.TYPE) { | 
 | 
                    return "(C)Ljava/lang/String;";  | 
 | 
                } else if (cl == Double.TYPE) { | 
 | 
                    return "(D)Ljava/lang/String;";  | 
 | 
                } else if (cl == Float.TYPE) { | 
 | 
                    return "(F)Ljava/lang/String;";  | 
 | 
                } else if (cl == Long.TYPE) { | 
 | 
                    return "(J)Ljava/lang/String;";  | 
 | 
                } else { | 
 | 
                    throw new IllegalStateException("Unhandled String.valueOf: " + cl); | 
 | 
                }  | 
 | 
            } else if (cl == String.class) { | 
 | 
                return "(Ljava/lang/String;)Ljava/lang/String;";  | 
 | 
            } else { | 
 | 
                return "(Ljava/lang/Object;)Ljava/lang/String;";  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        private static void iconst(MethodVisitor mv, final int cst) { | 
 | 
            if (cst >= -1 && cst <= 5) { | 
 | 
                mv.visitInsn(Opcodes.ICONST_0 + cst);  | 
 | 
            } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { | 
 | 
                mv.visitIntInsn(Opcodes.BIPUSH, cst);  | 
 | 
            } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { | 
 | 
                mv.visitIntInsn(Opcodes.SIPUSH, cst);  | 
 | 
            } else { | 
 | 
                mv.visitLdcInsn(cst);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int getLoadOpcode(Class<?> c) { | 
 | 
            if (c == Void.TYPE) { | 
 | 
                throw new InternalError("Unexpected void type of load opcode"); | 
 | 
            }  | 
 | 
            return ILOAD + getOpcodeOffset(c);  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int getOpcodeOffset(Class<?> c) { | 
 | 
            if (c.isPrimitive()) { | 
 | 
                if (c == Long.TYPE) { | 
 | 
                    return 1;  | 
 | 
                } else if (c == Float.TYPE) { | 
 | 
                    return 2;  | 
 | 
                } else if (c == Double.TYPE) { | 
 | 
                    return 3;  | 
 | 
                }  | 
 | 
                return 0;  | 
 | 
            } else { | 
 | 
                return 4;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int getParameterSize(Class<?> c) { | 
 | 
            if (c == Void.TYPE) { | 
 | 
                return 0;  | 
 | 
            } else if (c == Long.TYPE || c == Double.TYPE) { | 
 | 
                return 2;  | 
 | 
            }  | 
 | 
            return 1;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final class MethodHandleStringBuilderStrategy { | 
 | 
 | 
 | 
        private MethodHandleStringBuilderStrategy() { | 
 | 
            // no instantiation  | 
 | 
        }  | 
 | 
 | 
 | 
        private static MethodHandle generate(MethodType mt, Recipe recipe, Mode mode) throws Exception { | 
 | 
            int pc = mt.parameterCount();  | 
 | 
 | 
 | 
            Class<?>[] ptypes = mt.parameterArray();  | 
 | 
            MethodHandle[] filters = new MethodHandle[ptypes.length];  | 
 | 
            for (int i = 0; i < ptypes.length; i++) { | 
 | 
                MethodHandle filter;  | 
 | 
                switch (mode) { | 
 | 
                    case SIZED:  | 
 | 
                        // In sized mode, we convert all references and floats/doubles  | 
 | 
                        // to String: there is no specialization for different  | 
 | 
                        // classes in StringBuilder API, and it will convert to  | 
 | 
                          | 
 | 
                        filter = Stringifiers.forMost(ptypes[i]);  | 
 | 
                        break;  | 
 | 
                    case SIZED_EXACT:  | 
 | 
                        // In exact mode, we convert everything to String:  | 
 | 
                          | 
 | 
                        filter = Stringifiers.forAny(ptypes[i]);  | 
 | 
                        break;  | 
 | 
                    default:  | 
 | 
                        throw new StringConcatException("Not supported"); | 
 | 
                }  | 
 | 
                if (filter != null) { | 
 | 
                    filters[i] = filter;  | 
 | 
                    ptypes[i] = filter.type().returnType();  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            MethodHandle[] lengthers = new MethodHandle[pc];  | 
 | 
 | 
 | 
            // Figure out lengths: constants' lengths can be deduced on the spot.  | 
 | 
            // All reference arguments were filtered to String in the combinators below, so we can  | 
 | 
              | 
 | 
            int initial = 0;  | 
 | 
            for (RecipeElement el : recipe.getElements()) { | 
 | 
                switch (el.getTag()) { | 
 | 
                    case TAG_CONST:  | 
 | 
                        initial += el.getValue().length();  | 
 | 
                        break;  | 
 | 
                    case TAG_ARG:  | 
 | 
                        final int i = el.getArgPos();  | 
 | 
                        Class<?> type = ptypes[i];  | 
 | 
                        if (type.isPrimitive()) { | 
 | 
                            MethodHandle est = MethodHandles.constant(int.class, estimateSize(type));  | 
 | 
                            est = MethodHandles.dropArguments(est, 0, type);  | 
 | 
                            lengthers[i] = est;  | 
 | 
                        } else { | 
 | 
                            lengthers[i] = STRING_LENGTH;  | 
 | 
                        }  | 
 | 
                        break;  | 
 | 
                    default:  | 
 | 
                        throw new StringConcatException("Unhandled tag: " + el.getTag()); | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            MethodHandle builder = MethodHandles.dropArguments(MethodHandles.identity(StringBuilder.class), 1, ptypes);  | 
 | 
 | 
 | 
            // Compose append calls. This is done in reverse because the application order is  | 
 | 
              | 
 | 
            List<RecipeElement> elements = recipe.getElements();  | 
 | 
            for (int i = elements.size() - 1; i >= 0; i--) { | 
 | 
                RecipeElement el = elements.get(i);  | 
 | 
                MethodHandle appender;  | 
 | 
                switch (el.getTag()) { | 
 | 
                    case TAG_CONST:  | 
 | 
                        MethodHandle mh = appender(adaptToStringBuilder(String.class));  | 
 | 
                        appender = MethodHandles.insertArguments(mh, 1, el.getValue());  | 
 | 
                        break;  | 
 | 
                    case TAG_ARG:  | 
 | 
                        int ac = el.getArgPos();  | 
 | 
                        appender = appender(ptypes[ac]);  | 
 | 
 | 
 | 
                        // Insert dummy arguments to match the prefix in the signature.  | 
 | 
                          | 
 | 
                        if (ac != 0) { | 
 | 
                            appender = MethodHandles.dropArguments(appender, 1, Arrays.copyOf(ptypes, ac));  | 
 | 
                        }  | 
 | 
                        break;  | 
 | 
                    default:  | 
 | 
                        throw new StringConcatException("Unhandled tag: " + el.getTag()); | 
 | 
                }  | 
 | 
                builder = MethodHandles.foldArguments(builder, appender);  | 
 | 
            }  | 
 | 
 | 
 | 
            // Build the sub-tree that adds the sizes and produces a StringBuilder:  | 
 | 
 | 
 | 
            // a) Start with the reducer that accepts all arguments, plus one  | 
 | 
            //    slot for the initial value. Inject the initial value right away.  | 
 | 
              | 
 | 
            MethodHandle sum = getReducerFor(pc + 1);  | 
 | 
            MethodHandle adder = MethodHandles.insertArguments(sum, 0, initial);  | 
 | 
 | 
 | 
              | 
 | 
            adder = MethodHandles.filterArguments(adder, 0, lengthers);  | 
 | 
 | 
 | 
              | 
 | 
            MethodHandle newBuilder = MethodHandles.filterReturnValue(adder, NEW_STRING_BUILDER);  | 
 | 
 | 
 | 
              | 
 | 
            MethodHandle mh = MethodHandles.foldArguments(builder, newBuilder);  | 
 | 
 | 
 | 
              | 
 | 
            mh = MethodHandles.filterArguments(mh, 0, filters);  | 
 | 
 | 
 | 
              | 
 | 
            if (DEBUG && mode.isExact()) { | 
 | 
                mh = MethodHandles.filterReturnValue(mh, BUILDER_TO_STRING_CHECKED);  | 
 | 
            } else { | 
 | 
                mh = MethodHandles.filterReturnValue(mh, BUILDER_TO_STRING);  | 
 | 
            }  | 
 | 
 | 
 | 
            return mh;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static MethodHandle getReducerFor(int cnt) { | 
 | 
            return SUMMERS.computeIfAbsent(cnt, SUMMER);  | 
 | 
        }  | 
 | 
 | 
 | 
        private static MethodHandle appender(Class<?> appendType) { | 
 | 
            MethodHandle appender = lookupVirtual(MethodHandles.publicLookup(), StringBuilder.class, "append",  | 
 | 
                    StringBuilder.class, adaptToStringBuilder(appendType));  | 
 | 
 | 
 | 
              | 
 | 
            MethodType nt = MethodType.methodType(void.class, StringBuilder.class, appendType);  | 
 | 
            return appender.asType(nt);  | 
 | 
        }  | 
 | 
 | 
 | 
        private static String toStringChecked(StringBuilder sb) { | 
 | 
            String s = sb.toString();  | 
 | 
            if (s.length() != sb.capacity()) { | 
 | 
                throw new AssertionError("Exactness check failed: result length = " + s.length() + ", buffer capacity = " + sb.capacity()); | 
 | 
            }  | 
 | 
            return s;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int sum(int v1, int v2) { | 
 | 
            return v1 + v2;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int sum(int v1, int v2, int v3) { | 
 | 
            return v1 + v2 + v3;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int sum(int v1, int v2, int v3, int v4) { | 
 | 
            return v1 + v2 + v3 + v4;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int sum(int v1, int v2, int v3, int v4, int v5) { | 
 | 
            return v1 + v2 + v3 + v4 + v5;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int sum(int v1, int v2, int v3, int v4, int v5, int v6) { | 
 | 
            return v1 + v2 + v3 + v4 + v5 + v6;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int sum(int v1, int v2, int v3, int v4, int v5, int v6, int v7) { | 
 | 
            return v1 + v2 + v3 + v4 + v5 + v6 + v7;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int sum(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8) { | 
 | 
            return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int sum(int initial, int[] vs) { | 
 | 
            int sum = initial;  | 
 | 
            for (int v : vs) { | 
 | 
                sum += v;  | 
 | 
            }  | 
 | 
            return sum;  | 
 | 
        }  | 
 | 
 | 
 | 
        private static final ConcurrentMap<Integer, MethodHandle> SUMMERS;  | 
 | 
 | 
 | 
          | 
 | 
        private static final Function<Integer, MethodHandle> SUMMER = new Function<Integer, MethodHandle>() { | 
 | 
            @Override  | 
 | 
            public MethodHandle apply(Integer cnt) { | 
 | 
                if (cnt == 1) { | 
 | 
                    return MethodHandles.identity(int.class);  | 
 | 
                } else if (cnt <= 8) { | 
 | 
                    // Variable-arity collectors are not as efficient as small-count methods,  | 
 | 
                      | 
 | 
                    Class<?>[] cls = new Class<?>[cnt];  | 
 | 
                    Arrays.fill(cls, int.class);  | 
 | 
                    return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, cls);  | 
 | 
                } else { | 
 | 
                    return lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleStringBuilderStrategy.class, "sum", int.class, int.class, int[].class)  | 
 | 
                            .asCollector(int[].class, cnt - 1);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        };  | 
 | 
 | 
 | 
        private static final MethodHandle NEW_STRING_BUILDER, STRING_LENGTH, BUILDER_TO_STRING, BUILDER_TO_STRING_CHECKED;  | 
 | 
 | 
 | 
        static { | 
 | 
            SUMMERS = new ConcurrentHashMap<>();  | 
 | 
            Lookup publicLookup = MethodHandles.publicLookup();  | 
 | 
            NEW_STRING_BUILDER = lookupConstructor(publicLookup, StringBuilder.class, int.class);  | 
 | 
            STRING_LENGTH = lookupVirtual(publicLookup, String.class, "length", int.class);  | 
 | 
            BUILDER_TO_STRING = lookupVirtual(publicLookup, StringBuilder.class, "toString", String.class);  | 
 | 
            if (DEBUG) { | 
 | 
                BUILDER_TO_STRING_CHECKED = lookupStatic(MethodHandles.Lookup.IMPL_LOOKUP,  | 
 | 
                        MethodHandleStringBuilderStrategy.class, "toStringChecked", String.class, StringBuilder.class);  | 
 | 
            } else { | 
 | 
                BUILDER_TO_STRING_CHECKED = null;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
    }  | 
 | 
 | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final class MethodHandleInlineCopyStrategy { | 
 | 
        static final Unsafe UNSAFE = Unsafe.getUnsafe();  | 
 | 
 | 
 | 
        private MethodHandleInlineCopyStrategy() { | 
 | 
            // no instantiation  | 
 | 
        }  | 
 | 
 | 
 | 
        static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable { | 
 | 
 | 
 | 
            // Create filters and obtain filtered parameter types. Filters would be used in the beginning  | 
 | 
            // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings).  | 
 | 
              | 
 | 
            Class<?>[] ptypes = mt.parameterArray();  | 
 | 
            MethodHandle[] filters = null;  | 
 | 
            for (int i = 0; i < ptypes.length; i++) { | 
 | 
                MethodHandle filter = Stringifiers.forMost(ptypes[i]);  | 
 | 
                if (filter != null) { | 
 | 
                    if (filters == null) { | 
 | 
                        filters = new MethodHandle[ptypes.length];  | 
 | 
                    }  | 
 | 
                    filters[i] = filter;  | 
 | 
                    ptypes[i] = filter.type().returnType();  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"  | 
 | 
            // with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up,  | 
 | 
            // which makes the code arguably hard to read.  | 
 | 
 | 
 | 
              | 
 | 
            MethodHandle mh;  | 
 | 
 | 
 | 
            mh = MethodHandles.dropArguments(NEW_STRING, 3, ptypes);  | 
 | 
 | 
 | 
            // Mix in prependers. This happens when (byte[], int, byte) = (storage, index, coder) is already  | 
 | 
            // known from the combinators below. We are assembling the string backwards, so "index" is the  | 
 | 
              | 
 | 
            for (RecipeElement el : recipe.getElements()) { | 
 | 
                  | 
 | 
                mh = MethodHandles.dropArguments(mh, 2, int.class);  | 
 | 
                switch (el.getTag()) { | 
 | 
                    case TAG_CONST: { | 
 | 
                        MethodHandle prepender = MethodHandles.insertArguments(prepender(String.class), 3, el.getValue());  | 
 | 
                        mh = MethodHandles.foldArguments(mh, 1, prepender,  | 
 | 
                                2, 0, 3   | 
 | 
                        );  | 
 | 
                        break;  | 
 | 
                    }  | 
 | 
                    case TAG_ARG: { | 
 | 
                        int pos = el.getArgPos();  | 
 | 
                        MethodHandle prepender = prepender(ptypes[pos]);  | 
 | 
                        mh = MethodHandles.foldArguments(mh, 1, prepender,  | 
 | 
                                2, 0, 3,   | 
 | 
                                4 + pos    | 
 | 
                        );  | 
 | 
                        break;  | 
 | 
                    }  | 
 | 
                    default:  | 
 | 
                        throw new StringConcatException("Unhandled tag: " + el.getTag()); | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            mh = MethodHandles.foldArguments(mh, 0, NEW_ARRAY,  | 
 | 
                    1, 2   | 
 | 
            );  | 
 | 
 | 
 | 
            // Start combining length and coder mixers.  | 
 | 
            //  | 
 | 
            // Length is easy: constant lengths can be computed on the spot, and all non-constant  | 
 | 
            // shapes have been either converted to Strings, or explicit methods for getting the  | 
 | 
            // string length out of primitives are provided.  | 
 | 
            //  | 
 | 
            // Coders are more interesting. Only Object, String and char arguments (and constants)  | 
 | 
            // can have non-Latin1 encoding. It is easier to blindly convert constants to String,  | 
 | 
            // and deduce the coder from there. Arguments would be either converted to Strings  | 
 | 
            // during the initial filtering, or handled by primitive specializations in CODER_MIXERS.  | 
 | 
            //  | 
 | 
            // The method handle shape after all length and coder mixers is:  | 
 | 
              | 
 | 
            byte initialCoder = INITIAL_CODER;  | 
 | 
            int initialLen = 0;      | 
 | 
            for (RecipeElement el : recipe.getElements()) { | 
 | 
                switch (el.getTag()) { | 
 | 
                    case TAG_CONST:  | 
 | 
                        String constant = el.getValue();  | 
 | 
                        initialCoder = (byte) coderMixer(String.class).invoke(initialCoder, constant);  | 
 | 
                        initialLen += constant.length();  | 
 | 
                        break;  | 
 | 
                    case TAG_ARG:  | 
 | 
                        int ac = el.getArgPos();  | 
 | 
 | 
 | 
                        Class<?> argClass = ptypes[ac];  | 
 | 
                        MethodHandle lm = lengthMixer(argClass);  | 
 | 
                        MethodHandle cm = coderMixer(argClass);  | 
 | 
 | 
 | 
                        // Read this bottom up:  | 
 | 
 | 
 | 
                          | 
 | 
                        mh = MethodHandles.dropArguments(mh, 2, int.class, byte.class);  | 
 | 
 | 
 | 
                        // 3. Compute "new-index", producing ("new-index", "new-coder", "old-index", "old-coder", <args>) | 
 | 
                          | 
 | 
                        mh = MethodHandles.foldArguments(mh, 0, lm,  | 
 | 
                                2,   | 
 | 
                                4 + ac   | 
 | 
                        );  | 
 | 
 | 
 | 
                        // 2. Compute "new-coder", producing ("new-coder", "old-index", "old-coder", <args>) | 
 | 
                          | 
 | 
                        mh = MethodHandles.foldArguments(mh, 0, cm,  | 
 | 
                                2,   | 
 | 
                                3 + ac   | 
 | 
                        );  | 
 | 
 | 
 | 
                          | 
 | 
                        break;  | 
 | 
                    default:  | 
 | 
                        throw new StringConcatException("Unhandled tag: " + el.getTag()); | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            // Insert initial lengths and coders here.  | 
 | 
              | 
 | 
            mh = MethodHandles.insertArguments(mh, 0, initialLen, initialCoder);  | 
 | 
 | 
 | 
              | 
 | 
            if (filters != null) { | 
 | 
                mh = MethodHandles.filterArguments(mh, 0, filters);  | 
 | 
            }  | 
 | 
 | 
 | 
            return mh;  | 
 | 
        }  | 
 | 
 | 
 | 
        @ForceInline  | 
 | 
        private static byte[] newArray(int length, byte coder) { | 
 | 
            return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder);  | 
 | 
        }  | 
 | 
 | 
 | 
        private static MethodHandle prepender(Class<?> cl) { | 
 | 
            return PREPENDERS.computeIfAbsent(cl, PREPEND);  | 
 | 
        }  | 
 | 
 | 
 | 
        private static MethodHandle coderMixer(Class<?> cl) { | 
 | 
            return CODER_MIXERS.computeIfAbsent(cl, CODER_MIX);  | 
 | 
        }  | 
 | 
 | 
 | 
        private static MethodHandle lengthMixer(Class<?> cl) { | 
 | 
            return LENGTH_MIXERS.computeIfAbsent(cl, LENGTH_MIX);  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() { | 
 | 
            @Override  | 
 | 
            public MethodHandle apply(Class<?> c) { | 
 | 
                return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", int.class, int.class, byte[].class, byte.class,  | 
 | 
                        Wrapper.asPrimitiveType(c));  | 
 | 
            }  | 
 | 
        };  | 
 | 
 | 
 | 
          | 
 | 
        private static final Function<Class<?>, MethodHandle> CODER_MIX = new Function<Class<?>, MethodHandle>() { | 
 | 
            @Override  | 
 | 
            public MethodHandle apply(Class<?> c) { | 
 | 
                return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mixCoder", byte.class, byte.class,  | 
 | 
                        Wrapper.asPrimitiveType(c));  | 
 | 
            }  | 
 | 
        };  | 
 | 
 | 
 | 
          | 
 | 
        private static final Function<Class<?>, MethodHandle> LENGTH_MIX = new Function<Class<?>, MethodHandle>() { | 
 | 
            @Override  | 
 | 
            public MethodHandle apply(Class<?> c) { | 
 | 
                return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mixLen", int.class, int.class,  | 
 | 
                        Wrapper.asPrimitiveType(c));  | 
 | 
            }  | 
 | 
        };  | 
 | 
 | 
 | 
        private static final MethodHandle NEW_STRING;  | 
 | 
        private static final MethodHandle NEW_ARRAY;  | 
 | 
        private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS;  | 
 | 
        private static final ConcurrentMap<Class<?>, MethodHandle> LENGTH_MIXERS;  | 
 | 
        private static final ConcurrentMap<Class<?>, MethodHandle> CODER_MIXERS;  | 
 | 
        private static final byte INITIAL_CODER;  | 
 | 
        static final Class<?> STRING_HELPER;  | 
 | 
 | 
 | 
        static { | 
 | 
            try { | 
 | 
                STRING_HELPER = Class.forName("java.lang.StringConcatHelper"); | 
 | 
                MethodHandle initCoder = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "initialCoder", byte.class);  | 
 | 
                INITIAL_CODER = (byte) initCoder.invoke();  | 
 | 
            } catch (Throwable e) { | 
 | 
                throw new AssertionError(e);  | 
 | 
            }  | 
 | 
 | 
 | 
            PREPENDERS = new ConcurrentHashMap<>();  | 
 | 
            LENGTH_MIXERS = new ConcurrentHashMap<>();  | 
 | 
            CODER_MIXERS = new ConcurrentHashMap<>();  | 
 | 
 | 
 | 
            NEW_STRING = lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "newString", String.class, byte[].class, int.class, byte.class);  | 
 | 
            NEW_ARRAY  = lookupStatic(Lookup.IMPL_LOOKUP, MethodHandleInlineCopyStrategy.class, "newArray", byte[].class, int.class, byte.class);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final class Stringifiers { | 
 | 
        private Stringifiers() { | 
 | 
            // no instantiation  | 
 | 
        }  | 
 | 
 | 
 | 
        private static class StringifierMost extends ClassValue<MethodHandle> { | 
 | 
            @Override  | 
 | 
            protected MethodHandle computeValue(Class<?> cl) { | 
 | 
                if (cl == String.class) { | 
 | 
                    return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, Object.class);  | 
 | 
                } else if (cl == float.class) { | 
 | 
                    return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, float.class);  | 
 | 
                } else if (cl == double.class) { | 
 | 
                    return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, double.class);  | 
 | 
                } else if (!cl.isPrimitive()) { | 
 | 
                    MethodHandle mhObject = lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, Object.class);  | 
 | 
 | 
 | 
                    // We need the additional conversion here, because String.valueOf(Object) may return null.  | 
 | 
                    // String conversion rules in Java state we need to produce "null" String in this case.  | 
 | 
                      | 
 | 
                    return MethodHandles.filterReturnValue(mhObject,  | 
 | 
                            mhObject.asType(MethodType.methodType(String.class, String.class)));  | 
 | 
                }  | 
 | 
 | 
 | 
                return null;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private static class StringifierAny extends ClassValue<MethodHandle> { | 
 | 
            @Override  | 
 | 
            protected MethodHandle computeValue(Class<?> cl) { | 
 | 
                if (cl == byte.class || cl == short.class || cl == int.class) { | 
 | 
                    return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, int.class);  | 
 | 
                } else if (cl == boolean.class) { | 
 | 
                    return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, boolean.class);  | 
 | 
                } else if (cl == char.class) { | 
 | 
                    return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, char.class);  | 
 | 
                } else if (cl == long.class) { | 
 | 
                    return lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, long.class);  | 
 | 
                } else { | 
 | 
                    MethodHandle mh = STRINGIFIERS_MOST.get(cl);  | 
 | 
                    if (mh != null) { | 
 | 
                        return mh;  | 
 | 
                    } else { | 
 | 
                        throw new IllegalStateException("Unknown class: " + cl); | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private static final ClassValue<MethodHandle> STRINGIFIERS_MOST = new StringifierMost();  | 
 | 
        private static final ClassValue<MethodHandle> STRINGIFIERS_ANY = new StringifierAny();  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        static MethodHandle forMost(Class<?> t) { | 
 | 
            return STRINGIFIERS_MOST.get(t);  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        static MethodHandle forAny(Class<?> t) { | 
 | 
            return STRINGIFIERS_ANY.get(t);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    /* ------------------------------- Common utilities ------------------------------------ */  | 
 | 
 | 
 | 
    static MethodHandle lookupStatic(Lookup lookup, Class<?> refc, String name, Class<?> rtype, Class<?>... ptypes) { | 
 | 
        try { | 
 | 
            return lookup.findStatic(refc, name, MethodType.methodType(rtype, ptypes));  | 
 | 
        } catch (NoSuchMethodException | IllegalAccessException e) { | 
 | 
            throw new AssertionError(e);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    static MethodHandle lookupVirtual(Lookup lookup, Class<?> refc, String name, Class<?> rtype, Class<?>... ptypes) { | 
 | 
        try { | 
 | 
            return lookup.findVirtual(refc, name, MethodType.methodType(rtype, ptypes));  | 
 | 
        } catch (NoSuchMethodException | IllegalAccessException e) { | 
 | 
            throw new AssertionError(e);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    static MethodHandle lookupConstructor(Lookup lookup, Class<?> refc, Class<?> ptypes) { | 
 | 
        try { | 
 | 
            return lookup.findConstructor(refc, MethodType.methodType(void.class, ptypes));  | 
 | 
        } catch (NoSuchMethodException | IllegalAccessException e) { | 
 | 
            throw new AssertionError(e);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    static int estimateSize(Class<?> cl) { | 
 | 
        if (cl == Integer.TYPE) { | 
 | 
            return 11;   | 
 | 
        } else if (cl == Boolean.TYPE) { | 
 | 
            return 5;   | 
 | 
        } else if (cl == Byte.TYPE) { | 
 | 
            return 4;   | 
 | 
        } else if (cl == Character.TYPE) { | 
 | 
            return 1;   | 
 | 
        } else if (cl == Short.TYPE) { | 
 | 
            return 6;   | 
 | 
        } else if (cl == Double.TYPE) { | 
 | 
            return 26;   | 
 | 
        } else if (cl == Float.TYPE) { | 
 | 
            return 26;   | 
 | 
        } else if (cl == Long.TYPE)  { | 
 | 
            return 20;   | 
 | 
        } else { | 
 | 
            throw new IllegalArgumentException("Cannot estimate the size for " + cl); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    static Class<?> adaptToStringBuilder(Class<?> c) { | 
 | 
        if (c.isPrimitive()) { | 
 | 
            if (c == Byte.TYPE || c == Short.TYPE) { | 
 | 
                return int.class;  | 
 | 
            }  | 
 | 
        } else { | 
 | 
            if (c != String.class) { | 
 | 
                return Object.class;  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return c;  | 
 | 
    }  | 
 | 
 | 
 | 
    private StringConcatFactory() { | 
 | 
        // no instantiation  | 
 | 
    }  | 
 | 
 | 
 | 
}  |