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}