A small patch to use the StringConcatFactory instead of String.format() to implement toString() of a data class

Remi Forax forax at univ-mlv.fr
Sun Nov 12 21:58:09 UTC 2017


Hi all,
A small patch to use the StringConcatFactory to implement the toString of a data class.

The patch manage the case where there is no getter like the current code, even if you can not create a data class with no field using javac.

cheers,
Rémi

diff -r a6a70e4541b3 src/java.base/share/classes/java/lang/invoke/ObjectMethodBuilders.java
--- a/src/java.base/share/classes/java/lang/invoke/ObjectMethodBuilders.java	Fri Nov 10 18:51:06 2017 +0100
+++ b/src/java.base/share/classes/java/lang/invoke/ObjectMethodBuilders.java	Sun Nov 12 22:54:42 2017 +0100
@@ -189,49 +189,50 @@
 
     /**
      * Generates a method handle for the {@code toString} method for a given data class
+     * @param lookup          the lookup object
      * @param receiverClass   the data class
      * @param getters         the list of getters
      * @param names           the names
      * @return the method handle
      */
-    private static MethodHandle makeToString(Class<?> receiverClass,
+    private static MethodHandle makeToString(MethodHandles.Lookup lookup,
+                                            Class<?> receiverClass,
                                             List<MethodHandle> getters,
                                             List<String> names) {
-        // This is a pretty lousy algorithm; we spread the receiver over N places,
-        // apply the N getters, apply N toString operations, and concat the result with String.format
-        // Better to use String.format directly, or delegate to StringConcatFactory
-        // Also probably want some quoting around String components
+        // This is a simple algorithm; we spread the receiver over N places,
+        // apply the N getters, then delegate to StringConcatFactory
+        // We may want some quoting around String components ?
 
         assert getters.size() == names.size();
 
-        int[] invArgs = new int[getters.size()];
-        Arrays.fill(invArgs, 0);
-        MethodHandle[] filters = new MethodHandle[getters.size()];
-        StringBuilder sb = new StringBuilder();
-        sb.append(receiverClass.getSimpleName()).append("[");
-        for (int i=0; i<getters.size(); i++) {
-            MethodHandle getter = getters.get(i); // (R)T
-            MethodHandle stringify = stringifier(getter.type().returnType()); // (T)String
-            MethodHandle stringifyThisField = MethodHandles.filterArguments(stringify, 0, getter);    // (R)String
-            filters[i] = stringifyThisField;
-            sb.append(names.get(i)).append("=%s");
-            if (i != getters.size() - 1)
-                sb.append(", ");
-        }
-        sb.append(']');
-        String formatString = sb.toString();
-        MethodHandle formatter = MethodHandles.insertArguments(STRING_FORMAT, 0, formatString)
-                                              .asCollector(String[].class, getters.size()); // (R*)String
-        if (getters.size() == 0) {
-            // Add back extra R
-            formatter = MethodHandles.dropArguments(formatter, 0, receiverClass);
-        }
-        else {
-            MethodHandle filtered = MethodHandles.filterArguments(formatter, 0, filters);
-            formatter = MethodHandles.permuteArguments(filtered, MethodType.methodType(String.class, receiverClass), invArgs);
+        if (getters.isEmpty()) {
+            return MethodHandles.dropArguments(
+                MethodHandles.constant(String.class, "[]"),
+                0, receiverClass);
         }
 
-        return formatter;
+        Class<?>[] getterTypes = new Class<?>[getters.size()];
+        StringBuilder recipeBuilder = new StringBuilder();
+        recipeBuilder.append(receiverClass.getSimpleName()).append('[');
+        String separator = "";
+        for (int i = 0; i < getters.size(); i++) {
+            recipeBuilder.append(separator).append(names.get(i)).append("=\1");
+            getterTypes[i] = getters.get(i).type().returnType();
+            separator = ", ";
+        }
+        String recipe = recipeBuilder.append(']').toString();
+        MethodType concatType = MethodType.methodType(String.class, getterTypes);
+
+        CallSite cs;
+        try {
+            cs = StringConcatFactory.makeConcatWithConstants(lookup, "toString", concatType, recipe);
+        } catch (StringConcatException e) {
+            throw (LinkageError)new LinkageError().initCause(e);
+        }
+        MethodHandle target = cs.dynamicInvoker();
+        target = MethodHandles.filterArguments(target, 0, getters.toArray(new MethodHandle[0]));
+        target = MethodHandles.permuteArguments(target, MethodType.methodType(String.class, receiverClass), new int[getters.size()]);
+        return target;
     }
 
     /**
@@ -277,6 +278,6 @@
      */
     public static CallSite makeToString(MethodHandles.Lookup lookup, String invName, MethodType invType,
                                         Class<?> dataClass, String names, MethodHandle... getters) throws Throwable {
-        return new ConstantCallSite(makeToString(dataClass, List.of(getters), List.of(names.split(";"))));
+        return new ConstantCallSite(makeToString(lookup, dataClass, List.of(getters), List.of(names.split(";"))));
     }
 }


More information about the amber-dev mailing list