001/*
002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010package org.eclipse.golo.runtime;
011
012import gololang.annotations.DecoratedBy;
013
014import java.lang.invoke.MethodHandle;
015import java.lang.invoke.MethodHandles;
016import java.lang.invoke.MethodHandles.Lookup;
017import java.lang.invoke.MethodType;
018import java.lang.reflect.Method;
019
020import static java.lang.invoke.MethodType.methodType;
021
022public final class DecoratorsHelper {
023
024  private static final MethodHandle FUNCTION_REFERENCE_TO_METHODHANDLE;
025  private static final MethodHandle INVOKE_WITH_ARGUMENTS;
026
027  static {
028    try {
029      Lookup lookup = MethodHandles.lookup();
030      FUNCTION_REFERENCE_TO_METHODHANDLE = lookup.findStatic(
031          DecoratorsHelper.class,
032          "functionReferenceToMethodHandle",
033          MethodType.methodType(Object.class, Object.class));
034      INVOKE_WITH_ARGUMENTS = lookup.findVirtual(
035          MethodHandle.class,
036          "invokeWithArguments",
037          MethodType.methodType(Object.class, Object[].class));
038    } catch (NoSuchMethodException | IllegalAccessException e) {
039      throw new Error("Could not bootstrap the required method handles", e);
040    }
041  }
042
043  private DecoratorsHelper() {
044  }
045
046  public static boolean isMethodDecorated(Method method) {
047    return method.isAnnotationPresent(DecoratedBy.class);
048  }
049
050  public static Method getDecoratorMethod(Method decorated) {
051    try {
052      return decorated.getDeclaringClass().getDeclaredMethod(decorated.getAnnotation(DecoratedBy.class).value(), Object.class);
053    } catch (NoSuchMethodException | SecurityException ex) {
054      throw new IllegalStateException("Unable to get the decorator for a method marked as decorated", ex);
055    }
056  }
057
058  private static Object functionReferenceToMethodHandle(Object retValue) {
059    return ((gololang.FunctionReference) retValue).handle();
060  }
061
062  public static MethodHandle getDecoratedMethodHandle(Lookup caller, Method originalMethod, int arity) {
063    try {
064      Method decoratorMethod = getDecoratorMethod(originalMethod);
065      MethodHandle decorator = caller.unreflect(decoratorMethod);
066      decorator = MethodHandles.filterReturnValue(decorator, FUNCTION_REFERENCE_TO_METHODHANDLE);
067      MethodHandle original = caller.unreflect(originalMethod);
068      decorator = decorator.bindTo(new gololang.FunctionReference(original)).asType(methodType(MethodHandle.class));
069      if (arity < 0) {
070        MethodHandle combined = MethodHandles.foldArguments(INVOKE_WITH_ARGUMENTS, decorator);
071        return combined.asVarargsCollector(Object[].class);
072      } else {
073        MethodHandle invoker = MethodHandles.invoker(MethodType.genericMethodType(arity));
074        return MethodHandles.foldArguments(invoker, decorator);
075      }
076    } catch (IllegalAccessException ex) {
077      throw new IllegalStateException("Unable to get the decorator for a method marked as decorated", ex);
078    }
079  }
080
081  public static MethodHandle getDecoratedMethodHandle(Method originalMethod, int arity) {
082    return getDecoratedMethodHandle(MethodHandles.lookup(), originalMethod, arity);
083  }
084
085  public static MethodHandle getDecoratedMethodHandle(Method originalMethod) {
086    return getDecoratedMethodHandle(MethodHandles.lookup(), originalMethod, -1);
087  }
088}