1 /*
   2  * Copyright (c) 2017, 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 java.util.Arrays;
  29 import java.util.HashMap;
  30 import java.util.List;
  31 import java.util.Objects;
  32 
  33 /**
  34  * ObjectMethodBuilders
  35  *
  36  * @author Brian Goetz
  37  */
  38 public class ObjectMethodBuilders {
  39     private static final MethodType DESCRIPTOR_MT = MethodType.methodType(MethodType.class);
  40     private static final MethodType NAMES_MT = MethodType.methodType(List.class);
  41     private static final MethodHandle FALSE = MethodHandles.constant(boolean.class, false);
  42     private static final MethodHandle TRUE = MethodHandles.constant(boolean.class, true);
  43     private static final MethodHandle ZERO = MethodHandles.constant(int.class, 0);
  44     private static final MethodHandle CLASS_IS_INSTANCE;
  45     private static final MethodHandle OBJECT_EQUALS;
  46     private static final MethodHandle OBJECTS_EQUALS;
  47     private static final MethodHandle OBJECTS_HASHCODE;
  48     private static final MethodHandle OBJECTS_TOSTRING;
  49     private static final MethodHandle OBJECT_EQ;
  50     private static final MethodHandle OBJECT_HASHCODE;
  51     private static final MethodHandle OBJECT_TO_STRING;
  52     private static final MethodHandle STRING_FORMAT;
  53     private static final MethodHandle HASH_COMBINER;
  54 
  55     private static final HashMap<Class<?>, MethodHandle> primitiveEquals = new HashMap<>();
  56     private static final HashMap<Class<?>, MethodHandle> primitiveHashers = new HashMap<>();
  57     private static final HashMap<Class<?>, MethodHandle> primitiveToString = new HashMap<>();
  58 
  59     static {
  60         try {
  61             MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
  62             MethodHandles.Lookup lookup = MethodHandles.Lookup.IMPL_LOOKUP;
  63 
  64             CLASS_IS_INSTANCE = publicLookup.findVirtual(Class.class, "isInstance", MethodType.methodType(boolean.class, Object.class));
  65             OBJECT_EQUALS = publicLookup.findVirtual(Object.class, "equals", MethodType.methodType(boolean.class, Object.class));
  66             OBJECT_HASHCODE = publicLookup.findVirtual(Object.class, "hashCode", MethodType.fromMethodDescriptorString("()I", null));
  67             OBJECT_TO_STRING = publicLookup.findVirtual(Object.class, "toString", MethodType.methodType(String.class));
  68             STRING_FORMAT = publicLookup.findStatic(String.class, "format", MethodType.methodType(String.class, String.class, Object[].class));
  69             OBJECTS_EQUALS = publicLookup.findStatic(Objects.class, "equals", MethodType.methodType(boolean.class, Object.class, Object.class));
  70             OBJECTS_HASHCODE = publicLookup.findStatic(Objects.class, "hashCode", MethodType.methodType(int.class, Object.class));
  71             OBJECTS_TOSTRING = publicLookup.findStatic(Objects.class, "toString", MethodType.methodType(String.class, Object.class));
  72 
  73             OBJECT_EQ = lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.methodType(boolean.class, Object.class, Object.class));
  74             HASH_COMBINER = lookup.findStatic(ObjectMethodBuilders.class, "hashCombiner", MethodType.fromMethodDescriptorString("(II)I", null));
  75             primitiveEquals.put(byte.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(BB)Z", null)));
  76             primitiveEquals.put(short.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(SS)Z", null)));
  77             primitiveEquals.put(char.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(CC)Z", null)));
  78             primitiveEquals.put(int.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(II)Z", null)));
  79             primitiveEquals.put(long.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(JJ)Z", null)));
  80             primitiveEquals.put(float.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(FF)Z", null)));
  81             primitiveEquals.put(double.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(DD)Z", null)));
  82             primitiveEquals.put(boolean.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(ZZ)Z", null)));
  83 
  84             primitiveHashers.put(byte.class, lookup.findStatic(Byte.class, "hashCode", MethodType.fromMethodDescriptorString("(B)I", null)));
  85             primitiveHashers.put(short.class, lookup.findStatic(Short.class, "hashCode", MethodType.fromMethodDescriptorString("(S)I", null)));
  86             primitiveHashers.put(char.class, lookup.findStatic(Character.class, "hashCode", MethodType.fromMethodDescriptorString("(C)I", null)));
  87             primitiveHashers.put(int.class, lookup.findStatic(Integer.class, "hashCode", MethodType.fromMethodDescriptorString("(I)I", null)));
  88             primitiveHashers.put(long.class, lookup.findStatic(Long.class, "hashCode", MethodType.fromMethodDescriptorString("(J)I", null)));
  89             primitiveHashers.put(float.class, lookup.findStatic(Float.class, "hashCode", MethodType.fromMethodDescriptorString("(F)I", null)));
  90             primitiveHashers.put(double.class, lookup.findStatic(Double.class, "hashCode", MethodType.fromMethodDescriptorString("(D)I", null)));
  91             primitiveHashers.put(boolean.class, lookup.findStatic(Boolean.class, "hashCode", MethodType.fromMethodDescriptorString("(Z)I", null)));
  92 
  93             primitiveToString.put(byte.class, lookup.findStatic(Byte.class, "toString", MethodType.methodType(String.class, byte.class)));
  94             primitiveToString.put(short.class, lookup.findStatic(Short.class, "toString", MethodType.methodType(String.class, short.class)));
  95             primitiveToString.put(char.class, lookup.findStatic(Character.class, "toString", MethodType.methodType(String.class, char.class)));
  96             primitiveToString.put(int.class, lookup.findStatic(Integer.class, "toString", MethodType.methodType(String.class, int.class)));
  97             primitiveToString.put(long.class, lookup.findStatic(Long.class, "toString", MethodType.methodType(String.class, long.class)));
  98             primitiveToString.put(float.class, lookup.findStatic(Float.class, "toString", MethodType.methodType(String.class, float.class)));
  99             primitiveToString.put(double.class, lookup.findStatic(Double.class, "toString", MethodType.methodType(String.class, double.class)));
 100             primitiveToString.put(boolean.class, lookup.findStatic(Boolean.class, "toString", MethodType.methodType(String.class, boolean.class)));
 101         }
 102         catch (ReflectiveOperationException e) {
 103             throw new RuntimeException(e);
 104         }
 105     }
 106 
 107     private static int hashCombiner(int x, int y) {
 108         return x*31 + y;
 109     }
 110 
 111     private static boolean eq(Object a, Object b) { return a == b; }
 112     private static boolean eq(byte a, byte b) { return a == b; }
 113     private static boolean eq(short a, short b) { return a == b; }
 114     private static boolean eq(char a, char b) { return a == b; }
 115     private static boolean eq(int a, int b) { return a == b; }
 116     private static boolean eq(long a, long b) { return a == b; }
 117     private static boolean eq(float a, float b) { return Float.compare(a, b) == 0; }
 118     private static boolean eq(double a, double b) { return Double.compare(a, b) == 0; }
 119     private static boolean eq(boolean a, boolean b) { return a == b; }
 120 
 121     /** Get the method handle for combining two values of a given type */
 122     private static MethodHandle equalator(Class<?> clazz) {
 123         return (clazz.isPrimitive()
 124                 ? primitiveEquals.get(clazz)
 125                 : OBJECTS_EQUALS.asType(MethodType.methodType(boolean.class, clazz, clazz)));
 126     }
 127 
 128     /** Get the hasher for a value of a given type */
 129     private static MethodHandle hasher(Class<?> clazz) {
 130         return (clazz.isPrimitive()
 131                 ? primitiveHashers.get(clazz)
 132                 : OBJECTS_HASHCODE.asType(MethodType.methodType(int.class, clazz)));
 133     }
 134 
 135     /** Get the stringifier for a value of a given type */
 136     private static MethodHandle stringifier(Class<?> clazz) {
 137         return (clazz.isPrimitive()
 138                 ? primitiveToString.get(clazz)
 139                 : OBJECTS_TOSTRING.asType(MethodType.methodType(String.class, clazz)));
 140     }
 141 
 142     /**
 143      * Generates a method handle for the {@code equals} method for a given data class
 144      * @param receiverClass   the data class
 145      * @param getters         the list of getters
 146      * @return the method handle
 147      */
 148     private static MethodHandle makeEquals(Class<?> receiverClass,
 149                                           List<MethodHandle> getters) {
 150         MethodType rr = MethodType.methodType(boolean.class, receiverClass, receiverClass);
 151         MethodType ro = MethodType.methodType(boolean.class, receiverClass, Object.class);
 152         MethodHandle instanceFalse = MethodHandles.dropArguments(FALSE, 0, receiverClass, Object.class); // (RO)Z
 153         MethodHandle instanceTrue = MethodHandles.dropArguments(TRUE, 0, receiverClass, Object.class); // (RO)Z
 154         MethodHandle isSameObject = OBJECT_EQ.asType(ro); // (RO)Z
 155         MethodHandle isInstance = MethodHandles.dropArguments(CLASS_IS_INSTANCE.bindTo(receiverClass), 0, receiverClass); // (RO)Z
 156         MethodHandle accumulator = MethodHandles.dropArguments(TRUE, 0, receiverClass, receiverClass); // (RR)Z
 157 
 158         for (MethodHandle getter : getters) {
 159             MethodHandle equalator = equalator(getter.type().returnType()); // (TT)Z
 160             MethodHandle thisFieldEqual = MethodHandles.filterArguments(equalator, 0, getter, getter); // (RR)Z
 161             accumulator = MethodHandles.guardWithTest(thisFieldEqual, accumulator, instanceFalse.asType(rr));
 162         }
 163 
 164         return MethodHandles.guardWithTest(isSameObject,
 165                                            instanceTrue,
 166                                            MethodHandles.guardWithTest(isInstance, accumulator.asType(ro), instanceFalse));
 167     }
 168 
 169     /**
 170      * Generates a method handle for the {@code hashCode} method for a given data class
 171      * @param receiverClass   the data class
 172      * @param getters         the list of getters
 173      * @return the method handle
 174      */
 175     private static MethodHandle makeHashCode(Class<?> receiverClass,
 176                                             List<MethodHandle> getters) {
 177         MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass); // (R)I
 178 
 179         // @@@ Use loop combinator instead?
 180         for (MethodHandle getter : getters) {
 181             MethodHandle hasher = hasher(getter.type().returnType()); // (T)I
 182             MethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter);    // (R)I
 183             MethodHandle combineHashes = MethodHandles.filterArguments(HASH_COMBINER, 0, accumulator, hashThisField); // (RR)I
 184             accumulator = MethodHandles.permuteArguments(combineHashes, accumulator.type(), 0, 0); // adapt (R)I to (RR)I
 185         }
 186 
 187         return accumulator;
 188     }
 189 
 190     /**
 191      * Generates a method handle for the {@code toString} method for a given data class
 192      * @param receiverClass   the data class
 193      * @param getters         the list of getters
 194      * @param names           the names
 195      * @return the method handle
 196      */
 197     private static MethodHandle makeToString(Class<?> receiverClass,
 198                                             List<MethodHandle> getters,
 199                                             List<String> names) {
 200         // This is a pretty lousy algorithm; we spread the receiver over N places,
 201         // apply the N getters, apply N toString operations, and concat the result with String.format
 202         // Better to use String.format directly, or delegate to StringConcatFactory
 203         // Also probably want some quoting around String components
 204 
 205         assert getters.size() == names.size();
 206 
 207         int[] invArgs = new int[getters.size()];
 208         Arrays.fill(invArgs, 0);
 209         MethodHandle[] filters = new MethodHandle[getters.size()];
 210         StringBuilder sb = new StringBuilder();
 211         sb.append(receiverClass.getSimpleName()).append("[");
 212         for (int i=0; i<getters.size(); i++) {
 213             MethodHandle getter = getters.get(i); // (R)T
 214             MethodHandle stringify = stringifier(getter.type().returnType()); // (T)String
 215             MethodHandle stringifyThisField = MethodHandles.filterArguments(stringify, 0, getter);    // (R)String
 216             filters[i] = stringifyThisField;
 217             sb.append(names.get(i)).append("=%s");
 218             if (i != getters.size() - 1)
 219                 sb.append(", ");
 220         }
 221         sb.append(']');
 222         String formatString = sb.toString();
 223         MethodHandle formatter = MethodHandles.insertArguments(STRING_FORMAT, 0, formatString)
 224                                               .asCollector(String[].class, getters.size()); // (R*)String
 225         if (getters.size() == 0) {
 226             // Add back extra R
 227             formatter = MethodHandles.dropArguments(formatter, 0, receiverClass);
 228         }
 229         else {
 230             MethodHandle filtered = MethodHandles.filterArguments(formatter, 0, filters);
 231             formatter = MethodHandles.permuteArguments(filtered, MethodType.methodType(String.class, receiverClass), invArgs);
 232         }
 233 
 234         return formatter;
 235     }
 236 
 237     /**
 238      * Bootstrap method to generate the {@code equals}, {@code hashCode}, and {@code toString} methods for a given data class
 239      * @param lookup       the lookup
 240      * @param methodName   the method name
 241      * @param type         the descriptor type
 242      * @param theClass     the data class
 243      * @param names        the list of field names joined into a string, separated by ";"
 244      * @param getters      the list of getters
 245      * @return a call site if invoked by and indy or a method handle if invoked by a condy
 246      * @throws Throwable if any exception is thrown during call site construction
 247      */
 248     public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
 249                                    Class<?> theClass, String names, MethodHandle... getters) throws Throwable {
 250         MethodType methodType;
 251         if (type instanceof MethodType)
 252             methodType = (MethodType) type;
 253         else {
 254             methodType = null;
 255             if (!MethodHandle.class.equals(type))
 256                 throw new IllegalArgumentException(type.toString());
 257         }
 258         List<MethodHandle> getterList = List.of(getters);
 259         MethodHandle handle;
 260         switch (methodName) {
 261             case "equals":
 262                 // validate method type
 263                 handle = makeEquals(theClass, getterList);
 264                 return methodType != null ? new ConstantCallSite(handle) : handle;
 265             case "hashCode":
 266                 // validate method type
 267                 handle = makeHashCode(theClass, getterList);
 268                 return methodType != null ? new ConstantCallSite(handle) : handle;
 269             case "toString":
 270                 // validate method type
 271                 handle = makeToString(theClass, getterList, List.of(names.split(";")));
 272                 return methodType != null ? new ConstantCallSite(handle) : handle;
 273             default:
 274                 throw new IllegalArgumentException(methodName);
 275         }
 276     }
 277 }