001/* 002 * Copyright (c) 2012-2017 Institut National des Sciences Appliquées de Lyon (INSA-Lyon) 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 */ 009 010package org.eclipse.golo.runtime; 011 012import gololang.FunctionReference; 013 014import java.lang.invoke.*; 015import java.lang.reflect.Constructor; 016import java.lang.reflect.Field; 017import java.lang.reflect.Method; 018import java.util.List; 019 020import static java.lang.invoke.MethodHandles.Lookup; 021import static java.lang.invoke.MethodHandles.permuteArguments; 022import static java.lang.invoke.MethodType.methodType; 023import static java.lang.reflect.Modifier.isPrivate; 024import static java.lang.reflect.Modifier.isStatic; 025import static org.eclipse.golo.runtime.DecoratorsHelper.getDecoratedMethodHandle; 026import static org.eclipse.golo.runtime.DecoratorsHelper.isMethodDecorated; 027import static org.eclipse.golo.runtime.NamedArgumentsHelper.*; 028import static gololang.Messages.message; 029 030public final class FunctionCallSupport { 031 032 private FunctionCallSupport() { 033 throw new UnsupportedOperationException("Don't instantiate invokedynamic bootstrap class"); 034 } 035 036 static class FunctionCallSite extends MutableCallSite { 037 038 final Lookup callerLookup; 039 final String name; 040 final boolean constant; 041 final String[] argumentNames; 042 043 FunctionCallSite(MethodHandles.Lookup callerLookup, String name, MethodType type, boolean constant, String... argumentNames) { 044 super(type); 045 this.callerLookup = callerLookup; 046 this.name = name; 047 this.constant = constant; 048 this.argumentNames = argumentNames; 049 } 050 } 051 052 private static final MethodHandle FALLBACK; 053 private static final MethodHandle SAM_FILTER; 054 private static final MethodHandle FUNCTIONAL_INTERFACE_FILTER; 055 056 static { 057 try { 058 MethodHandles.Lookup lookup = MethodHandles.lookup(); 059 FALLBACK = lookup.findStatic( 060 FunctionCallSupport.class, 061 "fallback", 062 methodType(Object.class, FunctionCallSite.class, Object[].class)); 063 SAM_FILTER = lookup.findStatic( 064 FunctionCallSupport.class, 065 "samFilter", 066 methodType(Object.class, Class.class, Object.class)); 067 FUNCTIONAL_INTERFACE_FILTER = lookup.findStatic( 068 FunctionCallSupport.class, 069 "functionalInterfaceFilter", 070 methodType(Object.class, Lookup.class, Class.class, Object.class)); 071 } catch (NoSuchMethodException | IllegalAccessException e) { 072 throw new Error("Could not bootstrap the required method handles", e); 073 } 074 } 075 076 public static Object samFilter(Class<?> type, Object value) { 077 if (value instanceof FunctionReference) { 078 return MethodHandleProxies.asInterfaceInstance(type, ((FunctionReference) value).handle()); 079 } 080 return value; 081 } 082 083 public static Object functionalInterfaceFilter(Lookup caller, Class<?> type, Object value) throws Throwable { 084 if (value instanceof FunctionReference) { 085 return asFunctionalInterface(caller, type, ((FunctionReference) value).handle()); 086 } 087 return value; 088 } 089 090 public static Object asFunctionalInterface(Lookup caller, Class<?> type, MethodHandle handle) throws Throwable { 091 for (Method method : type.getMethods()) { 092 if (!method.isDefault() && !isStatic(method.getModifiers())) { 093 MethodType lambdaType = methodType(method.getReturnType(), method.getParameterTypes()); 094 CallSite callSite = LambdaMetafactory.metafactory( 095 caller, 096 method.getName(), 097 methodType(type), 098 lambdaType, 099 handle, 100 lambdaType); 101 return callSite.dynamicInvoker().invoke(); 102 } 103 } 104 throw new RuntimeException(message("handle_conversion_failed", handle, type)); 105 } 106 107 public static CallSite bootstrap(Lookup caller, String name, MethodType type, Object... bsmArgs) throws IllegalAccessException, ClassNotFoundException { 108 boolean constant = ((int) bsmArgs[0]) == 1; 109 String[] argumentNames = new String[bsmArgs.length - 1]; 110 for (int i = 0; i < bsmArgs.length - 1; i++) { 111 argumentNames[i] = (String) bsmArgs[i + 1]; 112 } 113 FunctionCallSite callSite = new FunctionCallSite( 114 caller, 115 name.replaceAll("#", "\\."), 116 type, 117 constant, 118 argumentNames); 119 MethodHandle fallbackHandle = FALLBACK 120 .bindTo(callSite) 121 .asCollector(Object[].class, type.parameterCount()) 122 .asType(type); 123 callSite.setTarget(fallbackHandle); 124 return callSite; 125 } 126 127 public static Object fallback(FunctionCallSite callSite, Object[] args) throws Throwable { 128 String functionName = callSite.name; 129 MethodType type = callSite.type(); 130 Lookup caller = callSite.callerLookup; 131 Class<?> callerClass = caller.lookupClass(); 132 String[] argumentNames = callSite.argumentNames; 133 134 MethodHandle handle = null; 135 Object result = findStaticMethodOrField(callerClass, functionName, args); 136 if (result == null) { 137 result = findClassWithStaticMethodOrField(callerClass, functionName, args); 138 } 139 if (result == null) { 140 result = findClassWithStaticMethodOrFieldFromImports(callerClass, functionName, args); 141 } 142 if (result == null) { 143 result = findClassWithConstructor(callerClass, functionName, args); 144 } 145 if (result == null) { 146 result = findClassWithConstructorFromImports(callerClass, functionName, args); 147 } 148 if (result == null) { 149 throw new NoSuchMethodError(functionName + type.toMethodDescriptorString()); 150 } 151 152 Class<?>[] types = null; 153 if (result instanceof Method) { 154 Method method = (Method) result; 155 checkLocalFunctionCallFromSameModuleAugmentation(method, callerClass.getName()); 156 if (isMethodDecorated(method)) { 157 handle = getDecoratedMethodHandle(caller, method, type.parameterCount()); 158 } else { 159 types = method.getParameterTypes(); 160 //TODO: improve varargs support on named arguments. Matching the last param type + according argument 161 if (isVarargsWithNames(method, types, args, argumentNames)) { 162 handle = caller.unreflect(method).asFixedArity().asType(type); 163 } else { 164 handle = caller.unreflect(method).asType(type); 165 } 166 } 167 handle = reorderArguments(method, handle, argumentNames); 168 } else if (result instanceof Constructor) { 169 Constructor<?> constructor = (Constructor<?>) result; 170 types = constructor.getParameterTypes(); 171 if (constructor.isVarArgs() && TypeMatching.isLastArgumentAnArray(types.length, args)) { 172 handle = caller.unreflectConstructor(constructor).asFixedArity().asType(type); 173 } else { 174 handle = caller.unreflectConstructor(constructor).asType(type); 175 } 176 } else { 177 Field field = (Field) result; 178 handle = caller.unreflectGetter(field).asType(type); 179 } 180 handle = insertSAMFilter(handle, callSite.callerLookup, types, 0); 181 182 if (callSite.constant) { 183 Object constantValue = handle.invokeWithArguments(args); 184 MethodHandle constant; 185 if (constantValue == null) { 186 constant = MethodHandles.constant(Object.class, null); 187 } else { 188 constant = MethodHandles.constant(constantValue.getClass(), constantValue); 189 } 190 constant = MethodHandles.dropArguments(constant, 0, type.parameterArray()); 191 callSite.setTarget(constant.asType(type)); 192 return constantValue; 193 } else { 194 callSite.setTarget(handle); 195 return handle.invokeWithArguments(args); 196 } 197 } 198 199 private static boolean isVarargsWithNames(Method method, Class<?>[] types, Object[] args, String[] argumentNames) { 200 return method.isVarArgs() 201 && ( 202 TypeMatching.isLastArgumentAnArray(types.length, args) 203 || argumentNames.length > 0); 204 } 205 206 private static int[] getArgumentsOrder(Method method, List<String> parameterNames, String[] argumentNames) { 207 int[] argumentsOrder = new int[parameterNames.size()]; 208 for (int i = 0; i < argumentNames.length; i++) { 209 int actualPosition = parameterNames.indexOf(argumentNames[i]); 210 checkArgumentPosition(actualPosition, argumentNames[i], method.getName() + parameterNames); 211 argumentsOrder[actualPosition] = i; 212 } 213 return argumentsOrder; 214 } 215 216 public static MethodHandle reorderArguments(Method method, MethodHandle handle, String[] argumentNames) { 217 if (argumentNames.length == 0) { return handle; } 218 if (hasNamedParameters(method)) { 219 return permuteArguments(handle, handle.type(), getArgumentsOrder(method, getParameterNames(method), argumentNames)); 220 } 221 Warnings.noParameterNames(method.getName(), argumentNames); 222 return handle; 223 } 224 225 public static MethodHandle insertSAMFilter(MethodHandle handle, Lookup caller, Class<?>[] types, int startIndex) { 226 if (types != null) { 227 for (int i = 0; i < types.length; i++) { 228 if (TypeMatching.isSAM(types[i])) { 229 handle = MethodHandles.filterArguments(handle, startIndex + i, SAM_FILTER.bindTo(types[i])); 230 } else if (TypeMatching.isFunctionalInterface(types[i])) { 231 handle = MethodHandles.filterArguments( 232 handle, 233 startIndex + i, 234 FUNCTIONAL_INTERFACE_FILTER.bindTo(caller).bindTo(types[i])); 235 } 236 } 237 } 238 return handle; 239 } 240 241 private static void checkLocalFunctionCallFromSameModuleAugmentation(Method method, String callerClassName) { 242 if (isPrivate(method.getModifiers()) && callerClassName.contains("$")) { 243 String prefix = callerClassName.substring(0, callerClassName.indexOf("$")); 244 if (method.getDeclaringClass().getName().equals(prefix)) { 245 method.setAccessible(true); 246 } 247 } 248 } 249 250 private static Object findClassWithConstructorFromImports(Class<?> callerClass, String classname, Object[] args) { 251 String[] imports = Module.imports(callerClass); 252 for (String imported : imports) { 253 Object result = findClassWithConstructor(callerClass, imported + "." + classname, args); 254 if (result != null) { 255 return result; 256 } 257 if (imported.endsWith(classname)) { 258 result = findClassWithConstructor(callerClass, imported, args); 259 if (result != null) { 260 return result; 261 } 262 } 263 } 264 return null; 265 } 266 267 private static Object findClassWithConstructor(Class<?> callerClass, String classname, Object[] args) { 268 try { 269 Class<?> targetClass = Class.forName(classname, true, callerClass.getClassLoader()); 270 for (Constructor<?> constructor : targetClass.getConstructors()) { 271 if (TypeMatching.argumentsMatch(constructor, args)) { 272 return constructor; 273 } 274 } 275 } catch (ClassNotFoundException ignored) { 276 // ignored to try the next strategy 277 } 278 return null; 279 } 280 281 private static Object findClassWithStaticMethodOrFieldFromImports(Class<?> callerClass, String functionName, Object[] args) { 282 String[] imports = Module.imports(callerClass); 283 String[] classAndMethod = null; 284 final int classAndMethodSeparator = functionName.lastIndexOf("."); 285 if (classAndMethodSeparator > 0) { 286 classAndMethod = new String[]{ 287 functionName.substring(0, classAndMethodSeparator), 288 functionName.substring(classAndMethodSeparator + 1) 289 }; 290 } 291 for (String importedClassName : imports) { 292 try { 293 Class<?> importedClass; 294 try { 295 importedClass = Class.forName(importedClassName, true, callerClass.getClassLoader()); 296 } catch (ClassNotFoundException expected) { 297 if (classAndMethod == null) { 298 throw expected; 299 } 300 importedClass = Class.forName(importedClassName + "." + classAndMethod[0], true, callerClass.getClassLoader()); 301 } 302 String lookup = (classAndMethod == null) ? functionName : classAndMethod[1]; 303 Object result = findStaticMethodOrField(importedClass, lookup, args); 304 if (result != null) { 305 return result; 306 } 307 } catch (ClassNotFoundException ignored) { 308 // ignored to try the next strategy 309 Warnings.unavailableClass(importedClassName, callerClass.getName()); 310 } 311 } 312 return null; 313 } 314 315 private static Object findClassWithStaticMethodOrField(Class<?> callerClass, String functionName, Object[] args) { 316 int methodClassSeparatorIndex = functionName.lastIndexOf("."); 317 if (methodClassSeparatorIndex >= 0) { 318 String className = functionName.substring(0, methodClassSeparatorIndex); 319 String methodName = functionName.substring(methodClassSeparatorIndex + 1); 320 try { 321 Class<?> targetClass = Class.forName(className, true, callerClass.getClassLoader()); 322 return findStaticMethodOrField(targetClass, methodName, args); 323 } catch (ClassNotFoundException ignored) { 324 // ignored to try the next strategy 325 Warnings.unavailableClass(className, callerClass.getName()); 326 } 327 } 328 return null; 329 } 330 331 private static Object findStaticMethodOrField(Class<?> klass, String name, Object[] arguments) { 332 for (Method method : klass.getDeclaredMethods()) { 333 if (methodMatches(name, arguments, method, false)) { 334 return method; 335 } 336 } 337 for (Method method : klass.getMethods()) { 338 if (methodMatches(name, arguments, method, false)) { 339 return method; 340 } 341 } 342 for (Method method : klass.getDeclaredMethods()) { 343 if (methodMatches(name, arguments, method, true)) { 344 return method; 345 } 346 } 347 for (Method method : klass.getMethods()) { 348 if (methodMatches(name, arguments, method, true)) { 349 return method; 350 } 351 } 352 if (arguments.length == 0) { 353 for (Field field : klass.getDeclaredFields()) { 354 if (fieldMatches(name, field)) { 355 return field; 356 } 357 } 358 for (Field field : klass.getFields()) { 359 if (fieldMatches(name, field)) { 360 return field; 361 } 362 } 363 } 364 return null; 365 } 366 367 private static boolean methodMatches(String name, Object[] arguments, Method method, boolean varargs) { 368 if (method.getName().equals(name) && isStatic(method.getModifiers())) { 369 if (isMethodDecorated(method)) { 370 return true; 371 } else { 372 if (TypeMatching.argumentsMatch(method, arguments, varargs)) { 373 return true; 374 } 375 } 376 } 377 return false; 378 } 379 380 private static boolean fieldMatches(String name, Field field) { 381 return field.getName().equals(name) && isStatic(field.getModifiers()); 382 } 383}